diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 630ef402..ecf3ecb8 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,3 +1,11 @@ +# Release 3.3.2 - (December 21, 2018) + + * Fix Qt history export bug + * Improve network timeouts + * Prepend server transaction_broadcast error messages with + explanatory message. Render error messages as plain text. + + # Release 3.3.1 - (December 20, 2018) * Qt: Fix invoices tab crash (#4941) diff --git a/contrib/deterministic-build/electrum-icons b/contrib/deterministic-build/electrum-icons index bce0d7a4..d586021b 160000 --- a/contrib/deterministic-build/electrum-icons +++ b/contrib/deterministic-build/electrum-icons @@ -1 +1 @@ -Subproject commit bce0d7a427ecf2106bf4d1ec56feb4067a50b234 +Subproject commit d586021ba0d4820d6587cff000756b3d035d4f08 diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index adbff6b8..5abf08fd 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -109,6 +109,13 @@ class ElectrumWindow(App): def toggle_oneserver(self, x): self.oneserver = not self.oneserver + proxy_str = StringProperty('') + def update_proxy_str(self, proxy: dict): + mode = proxy.get('mode') + host = proxy.get('host') + port = proxy.get('port') + self.proxy_str = (host + ':' + port) if mode else _('None') + def choose_server_dialog(self, popup): from .uix.dialogs.choice_dialog import ChoiceDialog protocol = 's' @@ -288,6 +295,7 @@ class ElectrumWindow(App): self.auto_connect = net_params.auto_connect self.oneserver = net_params.oneserver self.proxy_config = net_params.proxy if net_params.proxy else {} + self.update_proxy_str(self.proxy_config) self.plugins = kwargs.get('plugins', []) self.gui_object = kwargs.get('gui_object', None) @@ -667,6 +675,7 @@ class ElectrumWindow(App): self.tabs = self.root.ids['tabs'] def update_interfaces(self, dt): + net_params = self.network.get_parameters() self.num_nodes = len(self.network.get_interfaces()) self.num_chains = len(self.network.get_blockchains()) chain = self.network.blockchain() @@ -675,6 +684,10 @@ class ElectrumWindow(App): interface = self.network.interface if interface: self.server_host = interface.host + else: + self.server_host = str(net_params.host) + ' (connecting...)' + self.proxy_config = net_params.proxy or {} + self.update_proxy_str(self.proxy_config) def on_network_event(self, event, *args): Logger.info('network event: '+ event) @@ -924,8 +937,11 @@ class ElectrumWindow(App): self.wallet.invoices.save() self.update_tab('invoices') else: - msg = msg[:500] if msg else _('There was an error broadcasting the transaction.') - self.show_error(msg) + display_msg = _('The server returned an error when broadcasting the transaction.') + if msg: + display_msg += '\n' + msg + display_msg = display_msg[:500] + self.show_error(display_msg) if self.network and self.network.is_connected(): self.show_info(_('Sending')) diff --git a/electrum/gui/kivy/uix/ui_screens/network.kv b/electrum/gui/kivy/uix/ui_screens/network.kv index 31363ce2..38476ac7 100644 --- a/electrum/gui/kivy/uix/ui_screens/network.kv +++ b/electrum/gui/kivy/uix/ui_screens/network.kv @@ -24,10 +24,7 @@ Popup: CardSeparator SettingsItem: - proxy: app.proxy_config.get('mode') - host: app.proxy_config.get('host') - port: app.proxy_config.get('port') - title: _("Proxy") + ': ' + ((self.host +':' + self.port) if self.proxy else _('None')) + title: _("Proxy") + ': ' + app.proxy_str description: _('Proxy configuration') action: lambda x: app.popup_dialog('proxy') diff --git a/electrum/gui/kivy/uix/ui_screens/proxy.kv b/electrum/gui/kivy/uix/ui_screens/proxy.kv index 538caa05..9cde32c6 100644 --- a/electrum/gui/kivy/uix/ui_screens/proxy.kv +++ b/electrum/gui/kivy/uix/ui_screens/proxy.kv @@ -73,5 +73,4 @@ Popup: if proxy['mode']=='none': proxy = None net_params = net_params._replace(proxy=proxy) app.network.run_from_another_thread(app.network.set_parameters(net_params)) - app.proxy_config = proxy if proxy else {} nd.dismiss() diff --git a/electrum/gui/kivy/uix/ui_screens/server.kv b/electrum/gui/kivy/uix/ui_screens/server.kv index 1864e0fb..67ce0675 100644 --- a/electrum/gui/kivy/uix/ui_screens/server.kv +++ b/electrum/gui/kivy/uix/ui_screens/server.kv @@ -23,7 +23,7 @@ Popup: height: '36dp' size_hint_x: 3 size_hint_y: None - text: app.server_host + text: app.network.get_parameters().host Label: height: '36dp' size_hint_x: 1 @@ -36,7 +36,7 @@ Popup: height: '36dp' size_hint_x: 3 size_hint_y: None - text: app.server_port + text: app.network.get_parameters().port Widget Button: id: chooser diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py index fd1f5e11..904b864a 100644 --- a/electrum/gui/qt/address_list.py +++ b/electrum/gui/qt/address_list.py @@ -152,8 +152,11 @@ class AddressList(MyTreeView): is_multisig = isinstance(self.wallet, Multisig_Wallet) can_delete = self.wallet.can_delete_address() selected = self.selected_in_column(1) + if not selected: + return multi_select = len(selected) > 1 addrs = [self.model().itemFromIndex(item).text() for item in selected] + menu = QMenu() if not multi_select: idx = self.indexAt(position) col = idx.column() @@ -162,8 +165,6 @@ class AddressList(MyTreeView): return addr = addrs[0] - menu = QMenu() - if not multi_select: addr_column_title = self.model().horizontalHeaderItem(2).text() addr_idx = idx.sibling(idx.row(), 2) diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py index 1d30b3ee..fb81e227 100644 --- a/electrum/gui/qt/exception_window.py +++ b/electrum/gui/qt/exception_window.py @@ -57,7 +57,8 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin): collapse_info = QPushButton(_("Show report contents")) collapse_info.clicked.connect( lambda: self.msg_box(QMessageBox.NoIcon, - self, _("Report contents"), self.get_report_string())) + self, _("Report contents"), self.get_report_string(), + rich_text=True)) main_box.addWidget(collapse_info) diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index da2e4b89..e53a6238 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -89,6 +89,7 @@ class HistoryModel(QAbstractItemModel, PrintError): self.view = None # type: HistoryList self.transactions = OrderedDictWithIndex() self.tx_status_cache = {} # type: Dict[str, Tuple[int, str]] + self.summary = None def set_view(self, history_list: 'HistoryList'): # FIXME HistoryModel and HistoryList mutually depend on each other. diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 9bcccc1f..5f1830d4 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -86,6 +86,7 @@ class StatusBarButton(QPushButton): self.clicked.connect(self.onPress) self.func = func self.setIconSize(QSize(25,25)) + self.setCursor(QCursor(Qt.PointingHandCursor)) def onPress(self, checked=False): '''Drops the unwanted PyQt5 "checked" argument''' @@ -610,7 +611,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): _("Before reporting a bug, upgrade to the most recent version of Electrum (latest release or git HEAD), and include the version number in your report."), _("Try to explain not only what the bug is, but how it occurs.") ]) - self.show_message(msg, title="Electrum - " + _("Reporting Bugs")) + self.show_message(msg, title="Electrum - " + _("Reporting Bugs"), rich_text=True) def notify_transactions(self): if self.tx_notification_queue.qsize() == 0: @@ -1697,7 +1698,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.invoice_list.update() self.do_clear() else: - parent.show_error(msg) + display_msg = _('The server returned an error when broadcasting the transaction.') + if msg: + display_msg += '\n' + msg + parent.show_error(display_msg) WaitingDialog(self, _('Broadcasting transaction...'), broadcast_thread, broadcast_done, self.on_error) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index bfdf751a..a8358ba5 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -190,24 +190,24 @@ class MessageBoxMixin(object): parent, title or '', msg, buttons=Yes|No, defaultButton=No) == Yes - def show_warning(self, msg, parent=None, title=None): + def show_warning(self, msg, parent=None, title=None, **kwargs): return self.msg_box(QMessageBox.Warning, parent, - title or _('Warning'), msg) + title or _('Warning'), msg, **kwargs) - def show_error(self, msg, parent=None): + def show_error(self, msg, parent=None, **kwargs): return self.msg_box(QMessageBox.Warning, parent, - _('Error'), msg) + _('Error'), msg, **kwargs) - def show_critical(self, msg, parent=None, title=None): + def show_critical(self, msg, parent=None, title=None, **kwargs): return self.msg_box(QMessageBox.Critical, parent, - title or _('Critical Error'), msg) + title or _('Critical Error'), msg, **kwargs) - def show_message(self, msg, parent=None, title=None): + def show_message(self, msg, parent=None, title=None, **kwargs): return self.msg_box(QMessageBox.Information, parent, - title or _('Information'), msg) + title or _('Information'), msg, **kwargs) def msg_box(self, icon, parent, title, text, buttons=QMessageBox.Ok, - defaultButton=QMessageBox.NoButton): + defaultButton=QMessageBox.NoButton, rich_text=False): parent = parent or self.top_level_window() if type(icon) is QPixmap: d = QMessageBox(QMessageBox.Information, title, str(text), buttons, parent) @@ -216,7 +216,12 @@ class MessageBoxMixin(object): d = QMessageBox(icon, title, str(text), buttons, parent) d.setWindowModality(Qt.WindowModal) d.setDefaultButton(defaultButton) - d.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) + if rich_text: + d.setTextInteractionFlags(Qt.TextSelectableByMouse| Qt.LinksAccessibleByMouse) + d.setTextFormat(Qt.RichText) + else: + d.setTextInteractionFlags(Qt.TextSelectableByMouse) + d.setTextFormat(Qt.PlainText) return d.exec_() class WindowModalDialog(QDialog, MessageBoxMixin): diff --git a/electrum/gui/stdio.py b/electrum/gui/stdio.py index 7b4a41b9..e3808129 100644 --- a/electrum/gui/stdio.py +++ b/electrum/gui/stdio.py @@ -206,7 +206,9 @@ class ElectrumGui: try: self.network.run_from_another_thread(self.network.broadcast_transaction(tx)) except Exception as e: - print(repr(e)) + display_msg = _('The server returned an error when broadcasting the transaction.') + display_msg += '\n' + repr(e) + print(display_msg) else: print(_('Payment sent.')) #self.do_clear() diff --git a/electrum/gui/text.py b/electrum/gui/text.py index 0c31f46b..50d2361b 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -15,7 +15,7 @@ from electrum.storage import WalletStorage from electrum.network import NetworkParameters from electrum.interface import deserialize_server -_ = lambda x:x +_ = lambda x:x # i18n class ElectrumGui: @@ -370,7 +370,9 @@ class ElectrumGui: try: self.network.run_from_another_thread(self.network.broadcast_transaction(tx)) except Exception as e: - self.show_message(repr(e)) + display_msg = _('The server returned an error when broadcasting the transaction.') + display_msg += '\n' + repr(e) + self.show_message(display_msg) else: self.show_message(_('Payment sent.')) self.do_clear() diff --git a/electrum/network.py b/electrum/network.py index 1704e038..748cd46b 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -737,6 +737,7 @@ class Network(PrintError): timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent) out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout) if out != tx.txid(): + # note: this is untrusted input from the server raise Exception(out) return out # txid diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index fc188ad0..e30ad11c 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -26,7 +26,7 @@ import threading -from PyQt5.Qt import QVBoxLayout, QLabel +from PyQt5.QtWidgets import QVBoxLayout, QLabel from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE from electrum.gui.qt.util import * diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index 8496b1ed..cef051c5 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -1,9 +1,9 @@ from functools import partial import threading -from PyQt5.Qt import Qt -from PyQt5.Qt import QGridLayout, QInputDialog, QPushButton -from PyQt5.Qt import QVBoxLayout, QLabel +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton +from PyQt5.QtWidgets import QVBoxLayout, QLabel from electrum.gui.qt.util import * from electrum.i18n import _ diff --git a/electrum/plugins/ledger/auth2fa.py b/electrum/plugins/ledger/auth2fa.py index f6ddeab0..fbc44e14 100644 --- a/electrum/plugins/ledger/auth2fa.py +++ b/electrum/plugins/ledger/auth2fa.py @@ -7,7 +7,7 @@ from binascii import hexlify, unhexlify import websocket -from PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel +from PyQt5.QtWidgets import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel import PyQt5.QtCore as QtCore from PyQt5.QtWidgets import * diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py index cfcf85f8..1c55ae89 100644 --- a/electrum/plugins/revealer/qt.py +++ b/electrum/plugins/revealer/qt.py @@ -170,19 +170,22 @@ class Plugin(RevealerPlugin): code_id = self.versioned_seed.checksum dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id), "", self.get_path_to_revealer_file(), "", "
", - "
", "", _("Always check you backups.")])) + "
", "", _("Always check you backups.")]), + rich_text=True) dialog.close() def ext_warning(self, dialog): dialog.show_message(''.join(["",_("Warning"), ": ", - _("your seed extension will not be included in the encrypted backup.")])) + _("your seed extension will not be included in the encrypted backup.")]), + rich_text=True) dialog.close() def bdone(self, dialog): version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id), - "
","", self.get_path_to_revealer_file(), ''])) + "
","", self.get_path_to_revealer_file(), '']), + rich_text=True) def customtxt_limits(self): @@ -208,7 +211,8 @@ class Plugin(RevealerPlugin): .format(warning=_("Warning"), ver0=_("Revealers starting with 0 are not secure due to a vulnerability."), url=_("More info at: {}").format(f'{link}'), - risk=_("Proceed at your own risk."))) + risk=_("Proceed at your own risk.")), + rich_text=True) def cypherseed_dialog(self, window): self.warn_old_revealer() diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index 1a764be9..36bd4a52 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -1,9 +1,9 @@ from functools import partial import threading -from PyQt5.Qt import Qt -from PyQt5.Qt import QGridLayout, QInputDialog, QPushButton -from PyQt5.Qt import QVBoxLayout, QLabel +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton +from PyQt5.QtWidgets import QVBoxLayout, QLabel from electrum.gui.qt.util import * from electrum.i18n import _ diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index aaa6aa72..5045c738 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -1,9 +1,9 @@ from functools import partial import threading -from PyQt5.Qt import Qt -from PyQt5.Qt import QGridLayout, QInputDialog, QPushButton -from PyQt5.Qt import QVBoxLayout, QLabel +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton +from PyQt5.QtWidgets import QVBoxLayout, QLabel from electrum.gui.qt.util import * from electrum.i18n import _ diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py index 86887c96..c1220867 100644 --- a/electrum/plugins/trustedcoin/trustedcoin.py +++ b/electrum/plugins/trustedcoin/trustedcoin.py @@ -45,7 +45,7 @@ from electrum.mnemonic import Mnemonic from electrum.wallet import Multisig_Wallet, Deterministic_Wallet from electrum.i18n import _ from electrum.plugin import BasePlugin, hook -from electrum.util import NotEnoughFunds +from electrum.util import NotEnoughFunds, UserFacingException from electrum.storage import STO_EV_USER_PW from electrum.network import Network @@ -319,7 +319,13 @@ class Wallet_2fa(Multisig_Wallet): otp = int(otp) long_user_id, short_id = self.get_user_id() raw_tx = tx.serialize() - r = server.sign(short_id, raw_tx, otp) + try: + r = server.sign(short_id, raw_tx, otp) + except TrustedCoinException as e: + if e.status_code == 400: # invalid OTP + raise UserFacingException(_('Invalid one-time password.')) from e + else: + raise if r: raw_tx = r.get('transaction') tx.update(raw_tx) diff --git a/electrum/version.py b/electrum/version.py index eae7acd1..6b986aa4 100644 --- a/electrum/version.py +++ b/electrum/version.py @@ -1,5 +1,5 @@ -ELECTRUM_VERSION = '3.3.1' # version of the client package -APK_VERSION = '3.3.1.0' # read by buildozer.spec +ELECTRUM_VERSION = '3.3.2' # version of the client package +APK_VERSION = '3.3.2.0' # read by buildozer.spec PROTOCOL_VERSION = '1.4' # protocol version requested diff --git a/icons/clock1.png b/icons/clock1.png index 4850431b..4e6b04d7 100644 Binary files a/icons/clock1.png and b/icons/clock1.png differ diff --git a/icons/clock2.png b/icons/clock2.png index 4bbad054..ac47a295 100644 Binary files a/icons/clock2.png and b/icons/clock2.png differ diff --git a/icons/clock3.png b/icons/clock3.png index bb74995e..daa4a9f5 100644 Binary files a/icons/clock3.png and b/icons/clock3.png differ diff --git a/icons/clock4.png b/icons/clock4.png index e640dfd3..41393542 100644 Binary files a/icons/clock4.png and b/icons/clock4.png differ diff --git a/icons/clock5.pdn b/icons/clock5.pdn new file mode 100644 index 00000000..e2676e6e Binary files /dev/null and b/icons/clock5.pdn differ diff --git a/icons/clock5.png b/icons/clock5.png index 327eb483..64e1f74c 100644 Binary files a/icons/clock5.png and b/icons/clock5.png differ