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