Files
purple-electrumwallet/electrum/gui/qt/update_checker.py
T

145 lines
6.1 KiB
Python
Raw Normal View History

2019-02-04 18:45:42 +01:00
# Copyright (C) 2019 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import asyncio
import base64
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar,
QHBoxLayout, QPushButton, QDialog)
2019-02-04 18:45:42 +01:00
from electrum import version
from electrum import constants
from electrum import ecc
from electrum.i18n import _
2019-04-26 18:52:26 +02:00
from electrum.util import make_aiohttp_session
from electrum.logging import Logger
from electrum.network import Network
from electrum._vendor.distutils.version import StrictVersion
2019-02-04 18:45:42 +01:00
class UpdateCheck(QDialog, Logger):
2019-02-04 18:45:42 +01:00
url = "https://electrum.org/version"
download_url = "https://electrum.org/#download"
VERSION_ANNOUNCEMENT_SIGNING_KEYS = (
"13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P", # ThomasV (since 3.3.4)
"1Nxgk6NTooV4qZsX5fdqQwrLjYcsQZAfTg", # ghost43 (since 4.1.2)
2019-02-04 18:45:42 +01:00
)
def __init__(self, *, latest_version=None):
QDialog.__init__(self)
2019-02-04 18:45:42 +01:00
self.setWindowTitle('Electrum - ' + _('Update Check'))
self.content = QVBoxLayout()
self.content.setContentsMargins(*[10]*4)
self.heading_label = QLabel()
self.content.addWidget(self.heading_label)
self.detail_label = QLabel()
self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
self.detail_label.setOpenExternalLinks(True)
self.content.addWidget(self.detail_label)
self.pb = QProgressBar()
self.pb.setMaximum(0)
self.pb.setMinimum(0)
self.content.addWidget(self.pb)
versions = QHBoxLayout()
versions.addWidget(QLabel(_("Current version: {}".format(version.ELECTRUM_VERSION))))
self.latest_version_label = QLabel(_("Latest version: {}".format(" ")))
versions.addWidget(self.latest_version_label)
self.content.addLayout(versions)
self.update_view(latest_version)
self.update_check_thread = UpdateCheckThread()
2019-02-04 18:45:42 +01:00
self.update_check_thread.checked.connect(self.on_version_retrieved)
self.update_check_thread.failed.connect(self.on_retrieval_failed)
self.update_check_thread.start()
close_button = QPushButton(_("Close"))
close_button.clicked.connect(self.close)
self.content.addWidget(close_button)
self.setLayout(self.content)
self.show()
def on_version_retrieved(self, version):
self.update_view(version)
def on_retrieval_failed(self):
self.heading_label.setText('<h2>' + _("Update check failed") + '</h2>')
self.detail_label.setText(_("Sorry, but we were unable to check for updates. Please try again later."))
self.pb.hide()
@staticmethod
def is_newer(latest_version):
return latest_version > StrictVersion(version.ELECTRUM_VERSION)
def update_view(self, latest_version=None):
if latest_version:
self.pb.hide()
self.latest_version_label.setText(_("Latest version: {}".format(latest_version)))
if self.is_newer(latest_version):
self.heading_label.setText('<h2>' + _("There is a new update available") + '</h2>')
url = "<a href='{u}'>{u}</a>".format(u=UpdateCheck.download_url)
self.detail_label.setText(_("You can download the new version from {}.").format(url))
else:
self.heading_label.setText('<h2>' + _("Already up to date") + '</h2>')
self.detail_label.setText(_("You are already on the latest version of Electrum."))
else:
self.heading_label.setText('<h2>' + _("Checking for updates...") + '</h2>')
self.detail_label.setText(_("Please wait while Electrum checks for available updates."))
2019-04-26 18:52:26 +02:00
class UpdateCheckThread(QThread, Logger):
2019-02-04 18:45:42 +01:00
checked = pyqtSignal(object)
failed = pyqtSignal()
def __init__(self):
2019-04-26 18:52:26 +02:00
QThread.__init__(self)
Logger.__init__(self)
self.network = Network.get_instance()
2019-02-04 18:45:42 +01:00
async def get_update_info(self):
2020-02-04 18:34:24 +01:00
# note: Use long timeout here as it is not critical that we get a response fast,
# and it's bad not to get an update notification just because we did not wait enough.
async with make_aiohttp_session(proxy=self.network.proxy, timeout=120) as session:
2019-02-04 18:45:42 +01:00
async with session.get(UpdateCheck.url) as result:
signed_version_dict = await result.json(content_type=None)
# example signed_version_dict:
# {
# "version": "3.9.9",
# "signatures": {
# "1Lqm1HphuhxKZQEawzPse8gJtgjm9kUKT4": "IA+2QG3xPRn4HAIFdpu9eeaCYC7S5wS/sDxn54LJx6BdUTBpse3ibtfq8C43M7M1VfpGkD5tsdwl5C6IfpZD/gQ="
# }
# }
version_num = signed_version_dict['version']
sigs = signed_version_dict['signatures']
for address, sig in sigs.items():
if address not in UpdateCheck.VERSION_ANNOUNCEMENT_SIGNING_KEYS:
continue
sig = base64.b64decode(sig)
msg = version_num.encode('utf-8')
if ecc.verify_message_with_address(address=address, sig65=sig, message=msg,
net=constants.BitcoinMainnet):
2019-04-26 18:52:26 +02:00
self.logger.info(f"valid sig for version announcement '{version_num}' from address '{address}'")
2019-02-04 18:45:42 +01:00
break
else:
raise Exception('no valid signature for version announcement')
return StrictVersion(version_num.strip())
def run(self):
if not self.network:
2019-02-04 18:45:42 +01:00
self.failed.emit()
return
try:
update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), self.network.asyncio_loop).result()
2019-02-04 18:45:42 +01:00
except Exception as e:
2019-04-26 18:52:26 +02:00
self.logger.info(f"got exception: '{repr(e)}'")
2019-02-04 18:45:42 +01:00
self.failed.emit()
else:
self.checked.emit(update_info)