From d820f9ad374533ad67e362a7e2db2817f7c0edc6 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 28 Jan 2019 15:11:03 +0100 Subject: [PATCH 01/78] transaction: change default version to 2 --- electrum/tests/test_wallet_vertical.py | 37 +++++++++++++++++++------- electrum/transaction.py | 6 +++-- electrum/wallet.py | 8 +++--- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py index 5e50d313..70079329 100644 --- a/electrum/tests/test_wallet_vertical.py +++ b/electrum/tests/test_wallet_vertical.py @@ -570,7 +570,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet1 -> wallet2 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)] - tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_segwit()) @@ -590,7 +590,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet2 -> wallet1 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)] - tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) @@ -643,7 +643,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet1 -> wallet2 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)] - tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners self.assertFalse(tx.is_complete()) wallet1b.sign_transaction(tx, password=None) @@ -666,7 +666,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet2 -> wallet1 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)] - tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) @@ -734,7 +734,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet1 -> wallet2 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)] - tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) txid = tx.txid() tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners self.assertEqual(txid, tx.txid()) @@ -760,7 +760,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet2 -> wallet1 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)] - tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) txid = tx.txid() tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners self.assertEqual(txid, tx.txid()) @@ -814,7 +814,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet1 -> wallet2 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 1000000)] - tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) @@ -834,7 +834,7 @@ class TestWalletSending(TestCaseForTestnet): # wallet2 -> wallet1 outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 300000)] - tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000) + tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000, tx_version=1) self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_segwit()) @@ -874,6 +874,7 @@ class TestWalletSending(TestCaseForTestnet): tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000) tx.set_rbf(True) tx.locktime = 1325501 + tx.version = 1 wallet.sign_transaction(tx, password=None) self.assertTrue(tx.is_complete()) @@ -895,6 +896,7 @@ class TestWalletSending(TestCaseForTestnet): # bump tx tx = wallet.bump_fee(tx=Transaction(tx.serialize()), delta=5000) tx.locktime = 1325501 + tx.version = 1 self.assertFalse(tx.is_complete()) wallet.sign_transaction(tx, password=None) @@ -925,6 +927,7 @@ class TestWalletSending(TestCaseForTestnet): tx = wallet.cpfp(funding_tx, fee=50000) tx.set_rbf(True) tx.locktime = 1325502 + tx.version = 1 wallet.sign_transaction(tx, password=None) self.assertTrue(tx.is_complete()) @@ -960,6 +963,7 @@ class TestWalletSending(TestCaseForTestnet): tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000) tx.set_rbf(True) tx.locktime = 1325499 + tx.version = 1 wallet.sign_transaction(tx, password=None) self.assertTrue(tx.is_complete()) @@ -981,6 +985,7 @@ class TestWalletSending(TestCaseForTestnet): # bump tx tx = wallet.bump_fee(tx=Transaction(tx.serialize()), delta=5000) tx.locktime = 1325500 + tx.version = 1 self.assertFalse(tx.is_complete()) wallet.sign_transaction(tx, password=None) @@ -1011,6 +1016,7 @@ class TestWalletSending(TestCaseForTestnet): tx = wallet.cpfp(funding_tx, fee=50000) tx.set_rbf(True) tx.locktime = 1325501 + tx.version = 1 wallet.sign_transaction(tx, password=None) self.assertTrue(tx.is_complete()) @@ -1045,7 +1051,7 @@ class TestWalletSending(TestCaseForTestnet): privkeys = ['93NQ7CFbwTPyKDJLXe97jczw33fiLijam2SCZL3Uinz1NSbHrTu', ] network = NetworkMock() dest_addr = 'tb1q3ws2p0qjk5vrravv065xqlnkckvzcpclk79eu2' - tx = sweep(privkeys, network, config=None, recipient=dest_addr, fee=5000, locktime=1325785) + tx = sweep(privkeys, network, config=None, recipient=dest_addr, fee=5000, locktime=1325785, tx_version=1) tx_copy = Transaction(tx.serialize()) self.assertEqual('010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400', @@ -1090,6 +1096,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1446655 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertFalse(tx.is_segwit()) @@ -1132,6 +1139,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertFalse(tx.is_segwit()) @@ -1172,6 +1180,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325341 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertTrue(tx.is_segwit()) @@ -1213,6 +1222,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325341 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertTrue(tx.is_segwit()) @@ -1249,6 +1259,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1283,6 +1294,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1317,6 +1329,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1354,6 +1367,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1391,6 +1405,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1428,6 +1443,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325340 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1477,6 +1493,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325503 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1534,6 +1551,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325504 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) @@ -1593,6 +1611,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet): tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx.set_rbf(True) tx.locktime = 1325505 + tx.version = 1 self.assertFalse(tx.is_complete()) self.assertEqual(1, len(tx.inputs())) diff --git a/electrum/transaction.py b/electrum/transaction.py index 9222b528..32d2e4cc 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -679,7 +679,7 @@ class Transaction: self._inputs = None self._outputs = None # type: List[TxOutput] self.locktime = 0 - self.version = 1 + self.version = 2 # by default we assume this is a partial txn; # this value will get properly set when deserializing self.is_partial_originally = True @@ -787,11 +787,13 @@ class Transaction: return d @classmethod - def from_io(klass, inputs, outputs, locktime=0): + def from_io(klass, inputs, outputs, locktime=0, version=None): self = klass(None) self._inputs = inputs self._outputs = outputs self.locktime = locktime + if version is not None: + self.version = version self.BIP69_sort() return self diff --git a/electrum/wallet.py b/electrum/wallet.py index 260be490..0da7f55c 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -126,7 +126,7 @@ def sweep_preparations(privkeys, network: 'Network', imax=100): def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=None, imax=100, - *, locktime=None): + *, locktime=None, tx_version=None): inputs, keypairs = sweep_preparations(privkeys, network, imax) total = sum(i.get('value') for i in inputs) if fee is None: @@ -142,7 +142,7 @@ def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=N if locktime is None: locktime = get_locktime_for_new_transaction(network) - tx = Transaction.from_io(inputs, outputs, locktime=locktime) + tx = Transaction.from_io(inputs, outputs, locktime=locktime, version=tx_version) tx.set_rbf(True) tx.sign(keypairs) return tx @@ -719,10 +719,12 @@ class Abstract_Wallet(AddressSynchronizer): return tx def mktx(self, outputs, password, config, fee=None, change_addr=None, - domain=None, rbf=False, nonlocal_only=False): + domain=None, rbf=False, nonlocal_only=False, *, tx_version=None): coins = self.get_spendable_coins(domain, config, nonlocal_only=nonlocal_only) tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr) tx.set_rbf(rbf) + if tx_version is not None: + tx.version = tx_version self.sign_transaction(tx, password) return tx From 9bbea9bf2f257ac9feccd2b4acd170b10276437c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 30 Jan 2019 21:30:25 +0100 Subject: [PATCH 02/78] wallet: implement wait_for_address_history_to_change API --- electrum/address_synchronizer.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py index 846eb41f..5d3e4558 100644 --- a/electrum/address_synchronizer.py +++ b/electrum/address_synchronizer.py @@ -403,6 +403,7 @@ class AddressSynchronizer(PrintError): @profiler def load_local_history(self): self._history_local = {} # address -> set(txid) + self._address_history_changed_events = defaultdict(asyncio.Event) # address -> Event for txid in itertools.chain(self.txi, self.txo): self._add_tx_to_local_history(txid) @@ -542,6 +543,7 @@ class AddressSynchronizer(PrintError): cur_hist = self._history_local.get(addr, set()) cur_hist.add(txid) self._history_local[addr] = cur_hist + self._mark_address_history_changed(addr) def _remove_tx_from_local_history(self, txid): with self.transaction_lock: @@ -554,6 +556,21 @@ class AddressSynchronizer(PrintError): else: self._history_local[addr] = cur_hist + def _mark_address_history_changed(self, addr: str) -> None: + # history for this address changed, wake up coroutines: + self._address_history_changed_events[addr].set() + # clear event immediately so that coroutines can wait() for the next change: + self._address_history_changed_events[addr].clear() + + async def wait_for_address_history_to_change(self, addr: str) -> None: + """Wait until the server tells us about a new transaction related to addr. + + Unconfirmed and confirmed transactions are not distinguished, and so e.g. SPV + is not taken into account. + """ + assert self.is_mine(addr), "address needs to be is_mine to be watched" + await self._address_history_changed_events[addr].wait() + def add_unverified_tx(self, tx_hash, tx_height): if tx_hash in self.verified_tx: if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT): From c3996930492747d6980a6d6317bfced5213d0a23 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 31 Jan 2019 12:13:31 +0100 Subject: [PATCH 03/78] qt contact list: context menu fixups fixes #5048 fixes #5049 follow-up 9cff42328d5b6c92c87a2b41bb1f4e463c39aab7 --- electrum/gui/qt/contact_list.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py index aebfacf7..e10df1d4 100644 --- a/electrum/gui/qt/contact_list.py +++ b/electrum/gui/qt/contact_list.py @@ -62,18 +62,18 @@ class ContactList(MyTreeView): menu = QMenu() idx = self.indexAt(position) column = idx.column() or 0 - selected = self.selected_in_column(column) selected_keys = [] - for s_idx in selected: + for s_idx in self.selected_in_column(0): sel_key = self.model().itemFromIndex(s_idx).data(Qt.UserRole) selected_keys.append(sel_key) - if not selected or not idx.isValid(): + if not selected_keys or not idx.isValid(): menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog()) menu.addAction(_("Import file"), lambda: self.import_contacts()) menu.addAction(_("Export file"), lambda: self.export_contacts()) else: column_title = self.model().horizontalHeaderItem(column).text() - column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() for s_idx in selected) + column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() + for s_idx in self.selected_in_column(column)) menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data)) if column in self.editable_columns: item = self.model().itemFromIndex(idx) @@ -85,7 +85,7 @@ class ContactList(MyTreeView): menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(selected_keys)) URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, selected_keys)] if URLs: - menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs)) + menu.addAction(_("View on block explorer"), lambda: [webbrowser.open(u) for u in URLs]) run_hook('create_contact_menu', menu, selected_keys) menu.exec_(self.viewport().mapToGlobal(position)) From d4967faf2809ee477b3d887181b1708a6157d97a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 31 Jan 2019 16:37:27 +0100 Subject: [PATCH 04/78] wine build: pin wine signing key. minor refactoring. --- contrib/build-wine/docker/Dockerfile | 37 ++++++++++++++++------------ contrib/build-wine/docker/README.md | 2 +- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/contrib/build-wine/docker/Dockerfile b/contrib/build-wine/docker/Dockerfile index 784b9ef3..ab7d06d3 100644 --- a/contrib/build-wine/docker/Dockerfile +++ b/contrib/build-wine/docker/Dockerfile @@ -9,19 +9,10 @@ RUN dpkg --add-architecture i386 && \ gnupg2=2.2.4-1ubuntu1.2 \ dirmngr=2.2.4-1ubuntu1.2 \ python3-software-properties=0.96.24.32.1 \ - software-properties-common=0.96.24.32.1 \ - && \ - wget -nc https://dl.winehq.org/wine-builds/Release.key && \ - wget -nc https://dl.winehq.org/wine-builds/winehq.key && \ - apt-key add Release.key && \ - apt-key add winehq.key && \ - apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \ - apt-get update -q && \ - apt-get install -qy \ - wine-stable-amd64:amd64=3.0.1~bionic \ - wine-stable-i386:i386=3.0.1~bionic \ - wine-stable:amd64=3.0.1~bionic \ - winehq-stable:amd64=3.0.1~bionic \ + software-properties-common=0.96.24.32.1 + +RUN apt-get update -q && \ + apt-get install -qy \ git=1:2.17.1-1ubuntu0.4 \ p7zip-full=16.02+dfsg-6 \ make=4.1-9.1ubuntu1 \ @@ -29,8 +20,22 @@ RUN dpkg --add-architecture i386 && \ autotools-dev=20180224.1 \ autoconf=2.69-11 \ libtool=2.4.6-2 \ - gettext=0.19.8.1-6 \ - && \ - rm -rf /var/lib/apt/lists/* && \ + gettext=0.19.8.1-6 + +RUN wget -nc https://dl.winehq.org/wine-builds/Release.key && \ + echo "c51bcb8cc4a12abfbd7c7660eaf90f49674d15e222c262f27e6c96429111b822 Release.key" | sha256sum -c - && \ + apt-key add Release.key && \ + wget -nc https://dl.winehq.org/wine-builds/winehq.key && \ + echo "78b185fabdb323971d13bd329fefc8038e08559aa51c4996de18db0639a51df6 winehq.key" | sha256sum -c - && \ + apt-key add winehq.key && \ + apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \ + apt-get update -q && \ + apt-get install -qy \ + wine-stable-amd64:amd64=3.0.1~bionic \ + wine-stable-i386:i386=3.0.1~bionic \ + wine-stable:amd64=3.0.1~bionic \ + winehq-stable:amd64=3.0.1~bionic + +RUN rm -rf /var/lib/apt/lists/* && \ apt-get autoremove -y && \ apt-get clean diff --git a/contrib/build-wine/docker/README.md b/contrib/build-wine/docker/README.md index 9caf53f2..5996968b 100644 --- a/contrib/build-wine/docker/README.md +++ b/contrib/build-wine/docker/README.md @@ -20,7 +20,7 @@ folder. 2. Build image ``` - $ sudo docker build --no-cache -t electrum-wine-builder-img contrib/build-wine/docker + $ sudo docker build -t electrum-wine-builder-img contrib/build-wine/docker ``` Note: see [this](https://stackoverflow.com/a/40516974/7499128) if having dns problems From 3ca1b710d60c2192184702d4413f078fb402fa4b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 31 Jan 2019 17:01:00 +0100 Subject: [PATCH 05/78] build: use sha256sum instead of md5sum --- contrib/build-wine/build-electrum-git.sh | 2 +- contrib/osx/package.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 863ca733..7b44eeb9 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -75,4 +75,4 @@ mv electrum-setup.exe $NAME_ROOT-$VERSION-setup.exe cd .. echo "Done." -md5sum dist/electrum*exe +sha256sum dist/electrum*exe diff --git a/contrib/osx/package.sh b/contrib/osx/package.sh index dcbc2938..096ef02a 100755 --- a/contrib/osx/package.sh +++ b/contrib/osx/package.sh @@ -85,4 +85,4 @@ dmg dmg Electrum_uncompressed.dmg electrum-$VERSION.dmg || fail "Unable to creat rm Electrum_uncompressed.dmg echo "Done." -md5sum electrum-$VERSION.dmg +sha256sum electrum-$VERSION.dmg From e7d3fd32fbfc04d6b5b4bedadf1e4d1cf093b619 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 31 Jan 2019 19:44:04 +0100 Subject: [PATCH 06/78] contrib/freeze_packages.sh: should hard fail if there is an error exceptions raised by find_restricted_dependencies.py were getting ignored --- contrib/freeze_packages.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh index 19d6b5fc..2bc151f4 100755 --- a/contrib/freeze_packages.sh +++ b/contrib/freeze_packages.sh @@ -1,6 +1,8 @@ #!/bin/bash # Run this after a new release to update dependencies +set -e + venv_dir=~/.electrum-venv contrib=$(dirname "$0") From 7f3de8241c3856c2e873c5f6e9fa513f454e5d9b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 31 Jan 2019 20:58:04 +0100 Subject: [PATCH 07/78] qt/hww: temporarily bundle our own version of safetlib.qt.pinmatrix until safetlib releases a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e closes #4960 --- contrib/build-wine/deterministic.spec | 5 + contrib/osx/osx.spec | 5 + electrum/plugins/safe_t/pinmatrix.py | 138 ++++++++++++++++++++++++++ electrum/plugins/safe_t/qt.py | 5 +- 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 electrum/plugins/safe_t/pinmatrix.py diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 3429bc7b..2c461dd9 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -23,6 +23,11 @@ hiddenimports += collect_submodules('keepkeylib') hiddenimports += collect_submodules('websocket') hiddenimports += collect_submodules('ckcc') +# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they +# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e +hiddenimports.remove('safetlib.qt.pinmatrix') + + # Add libusb binary binaries = [(PYHOME+"/libusb-1.0.dll", ".")] diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec index 887ee01d..c86ee58e 100644 --- a/contrib/osx/osx.spec +++ b/contrib/osx/osx.spec @@ -66,6 +66,11 @@ hiddenimports += collect_submodules('keepkeylib') hiddenimports += collect_submodules('websocket') hiddenimports += collect_submodules('ckcc') +# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they +# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e +hiddenimports.remove('safetlib.qt.pinmatrix') + + datas = [ (electrum + PYPKG + '/*.json', PYPKG), (electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'), diff --git a/electrum/plugins/safe_t/pinmatrix.py b/electrum/plugins/safe_t/pinmatrix.py new file mode 100644 index 00000000..803d9807 --- /dev/null +++ b/electrum/plugins/safe_t/pinmatrix.py @@ -0,0 +1,138 @@ +# copy of https://raw.githubusercontent.com/archos-safe-t/python-safet/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e/trezorlib/qt/pinmatrix.py + +from __future__ import print_function +import sys +import math + +try: + from PyQt4.QtGui import (QPushButton, QLineEdit, QSizePolicy, QRegExpValidator, QLabel, + QApplication, QWidget, QGridLayout, QVBoxLayout, QHBoxLayout) + from PyQt4.QtCore import QObject, SIGNAL, QRegExp, Qt, QT_VERSION_STR +except: + from PyQt5.QtWidgets import (QPushButton, QLineEdit, QSizePolicy, QLabel, + QApplication, QWidget, QGridLayout, QVBoxLayout, QHBoxLayout) + from PyQt5.QtGui import QRegExpValidator + from PyQt5.QtCore import QRegExp, Qt, QT_VERSION_STR + + +class PinButton(QPushButton): + def __init__(self, password, encoded_value): + super(PinButton, self).__init__('?') + self.password = password + self.encoded_value = encoded_value + + if QT_VERSION_STR >= '5': + self.clicked.connect(self._pressed) + elif QT_VERSION_STR >= '4': + QObject.connect(self, SIGNAL('clicked()'), self._pressed) + else: + raise RuntimeError('Unsupported Qt version') + + def _pressed(self): + self.password.setText(self.password.text() + str(self.encoded_value)) + self.password.setFocus() + + +class PinMatrixWidget(QWidget): + ''' + Displays widget with nine blank buttons and password box. + Encodes button clicks into sequence of numbers for passing + into PinAck messages of TREZOR. + + show_strength=True may be useful for entering new PIN + ''' + def __init__(self, show_strength=True, parent=None): + super(PinMatrixWidget, self).__init__(parent) + + self.password = QLineEdit() + self.password.setValidator(QRegExpValidator(QRegExp('[1-9]+'), None)) + self.password.setEchoMode(QLineEdit.Password) + + if QT_VERSION_STR >= '5': + self.password.textChanged.connect(self._password_changed) + elif QT_VERSION_STR >= '4': + QObject.connect(self.password, SIGNAL('textChanged(QString)'), self._password_changed) + else: + raise RuntimeError('Unsupported Qt version') + + self.strength = QLabel() + self.strength.setMinimumWidth(75) + self.strength.setAlignment(Qt.AlignCenter) + self._set_strength(0) + + grid = QGridLayout() + grid.setSpacing(0) + for y in range(3)[::-1]: + for x in range(3): + button = PinButton(self.password, x + y * 3 + 1) + button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + button.setFocusPolicy(Qt.NoFocus) + grid.addWidget(button, 3 - y, x) + + hbox = QHBoxLayout() + hbox.addWidget(self.password) + if show_strength: + hbox.addWidget(self.strength) + + vbox = QVBoxLayout() + vbox.addLayout(grid) + vbox.addLayout(hbox) + self.setLayout(vbox) + + def _set_strength(self, strength): + if strength < 3000: + self.strength.setText('weak') + self.strength.setStyleSheet("QLabel { color : #d00; }") + elif strength < 60000: + self.strength.setText('fine') + self.strength.setStyleSheet("QLabel { color : #db0; }") + elif strength < 360000: + self.strength.setText('strong') + self.strength.setStyleSheet("QLabel { color : #0a0; }") + else: + self.strength.setText('ULTIMATE') + self.strength.setStyleSheet("QLabel { color : #000; font-weight: bold;}") + + def _password_changed(self, password): + self._set_strength(self.get_strength()) + + def get_strength(self): + digits = len(set(str(self.password.text()))) + strength = math.factorial(9) / math.factorial(9 - digits) + return strength + + def get_value(self): + return self.password.text() + + +if __name__ == '__main__': + ''' + Demo application showing PinMatrix widget in action + ''' + app = QApplication(sys.argv) + + matrix = PinMatrixWidget() + + def clicked(): + print("PinMatrix value is", matrix.get_value()) + print("Possible button combinations:", matrix.get_strength()) + sys.exit() + + ok = QPushButton('OK') + if QT_VERSION_STR >= '5': + ok.clicked.connect(clicked) + elif QT_VERSION_STR >= '4': + QObject.connect(ok, SIGNAL('clicked()'), clicked) + else: + raise RuntimeError('Unsupported Qt version') + + vbox = QVBoxLayout() + vbox.addWidget(matrix) + vbox.addWidget(ok) + + w = QWidget() + w.setLayout(vbox) + w.move(100, 100) + w.show() + + app.exec_() diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index f7d51f25..40e81fb1 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -174,7 +174,10 @@ class Plugin(SafeTPlugin, QtPlugin): @classmethod def pin_matrix_widget_class(self): - from safetlib.qt.pinmatrix import PinMatrixWidget + # We use a local updated copy of pinmatrix.py until safetlib + # releases a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e + # from safetlib.qt.pinmatrix import PinMatrixWidget + from .pinmatrix import PinMatrixWidget return PinMatrixWidget From 8716bc8cfb4e626b2c97885e443d0795ed52cbef Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Fri, 1 Feb 2019 06:41:15 +0000 Subject: [PATCH 08/78] Refactor for loop in UTXOList This refactor makes UTXOList somewhat easier to subclass. --- electrum/gui/qt/utxo_list.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 5bd1703c..36d037ad 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -47,22 +47,25 @@ class UTXOList(MyTreeView): self.model().clear() self.update_headers(self.__class__.headers) for idx, x in enumerate(utxos): - address = x.get('address') - height = x.get('height') - name = x.get('prevout_hash') + ":%d"%x.get('prevout_n') - self.utxo_dict[name] = x - label = self.wallet.get_label(x.get('prevout_hash')) - amount = self.parent.format_amount(x['value'], whitespaces=True) - labels = [address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]] - utxo_item = [QStandardItem(x) for x in labels] - self.set_editability(utxo_item) - utxo_item[0].setFont(QFont(MONOSPACE_FONT)) - utxo_item[2].setFont(QFont(MONOSPACE_FONT)) - utxo_item[4].setFont(QFont(MONOSPACE_FONT)) - utxo_item[0].setData(name, Qt.UserRole) - if self.wallet.is_frozen(address): - utxo_item[0].setBackground(ColorScheme.BLUE.as_color(True)) - self.model().insertRow(idx, utxo_item) + self.insert_utxo(idx, x) + + def insert_utxo(self, idx, x): + address = x.get('address') + height = x.get('height') + name = x.get('prevout_hash') + ":%d"%x.get('prevout_n') + self.utxo_dict[name] = x + label = self.wallet.get_label(x.get('prevout_hash')) + amount = self.parent.format_amount(x['value'], whitespaces=True) + labels = [address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]] + utxo_item = [QStandardItem(x) for x in labels] + self.set_editability(utxo_item) + utxo_item[0].setFont(QFont(MONOSPACE_FONT)) + utxo_item[2].setFont(QFont(MONOSPACE_FONT)) + utxo_item[4].setFont(QFont(MONOSPACE_FONT)) + utxo_item[0].setData(name, Qt.UserRole) + if self.wallet.is_frozen(address): + utxo_item[0].setBackground(ColorScheme.BLUE.as_color(True)) + self.model().insertRow(idx, utxo_item) def selected_column_0_user_roles(self) -> Optional[List[str]]: if not self.model(): From 3ad6f738bd15e2574b68251a633cd3c77caf7145 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 1 Feb 2019 18:07:28 +0100 Subject: [PATCH 09/78] util: rm hfu, cleaner bh2u --- electrum/keystore.py | 7 ++++--- electrum/util.py | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/electrum/keystore.py b/electrum/keystore.py index 8c7e80de..0b857527 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -26,6 +26,7 @@ from unicodedata import normalize import hashlib +import binascii from . import bitcoin, ecc, constants, bip32 from .bitcoin import (deserialize_privkey, serialize_privkey, @@ -35,9 +36,9 @@ from .bip32 import (bip32_public_derivation, deserialize_xpub, CKD_pub, bip32_private_key, bip32_derivation, BIP32_PRIME, is_xpub, is_xprv) from .ecc import string_to_number, number_to_string -from .crypto import (pw_decode, pw_encode, sha256d, PW_HASH_VERSION_LATEST, +from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST, SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion) -from .util import (PrintError, InvalidPassword, hfu, WalletFileException, +from .util import (PrintError, InvalidPassword, WalletFileException, BitcoinException, bh2u, bfh, print_error, inv_dict) from .mnemonic import Mnemonic, load_wordlist from .plugin import run_hook @@ -625,7 +626,7 @@ def bip39_is_checksum_valid(mnemonic): while len(h) < entropy_length/4: h = '0'+h b = bytearray.fromhex(h) - hashed = int(hfu(hashlib.sha256(b).digest()), 16) + hashed = int(binascii.hexlify(sha256(b)), 16) calculated_checksum = hashed >> (256 - checksum_length) return checksum == calculated_checksum, True diff --git a/electrum/util.py b/electrum/util.py index ac41d9e3..e4b556f8 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -462,7 +462,6 @@ def to_bytes(something, encoding='utf8') -> bytes: bfh = bytes.fromhex -hfu = binascii.hexlify def bh2u(x: bytes) -> str: @@ -473,7 +472,7 @@ def bh2u(x: bytes) -> str: >>> bh2u(x) '01020A' """ - return hfu(x).decode('ascii') + return x.hex() def user_dir(): From 185d02d9df38cc2da1e6ba76d2724e87d2478050 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 1 Feb 2019 19:46:20 +0100 Subject: [PATCH 10/78] delete snap file we don't distribute snaps; not sure if the file is useful for anyone it's already not working because it references python 3.5 --- snap/snapcraft.yaml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 snap/snapcraft.yaml diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml deleted file mode 100644 index 20f2b63d..00000000 --- a/snap/snapcraft.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: electrum -version: master -summary: Bitcoin thin client -description: | - Lightweight Bitcoin client - -grade: devel # must be 'stable' to release into candidate/stable channels -confinement: strict - -apps: - electrum: - command: desktop-launch electrum - plugs: [network, network-bind, x11, unity7] - -parts: - electrum: - source: . - plugin: python - python-version: python3 - stage-packages: [python3-pyqt5] - build-packages: [pyqt5-dev-tools] - install: pyrcc5 icons.qrc -o $SNAPCRAFT_PART_INSTALL/lib/python3.5/site-packages/electrum/gui/qt/icons_rc.py - after: [desktop-qt5] From 16bac5fd73a72df0d056a100f8778675cbe4885c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 1 Feb 2019 19:01:21 +0100 Subject: [PATCH 11/78] rm qt icons file so we don't need pyrcc5, which is not deterministic, and so we don't need the submodule for the icons based on electrumsv/electrumsv@bf8802c2eaf0bf75565b5423a95bcb85ec7eb781 --- .gitignore | 1 - .gitmodules | 3 - MANIFEST.in | 1 - README.rst | 8 --- contrib/build-wine/build-electrum-git.sh | 3 +- contrib/build-wine/deterministic.spec | 2 + .../deterministic-build/check_submodules.sh | 7 -- contrib/deterministic-build/electrum-icons | 1 - contrib/osx/make_osx | 1 - contrib/osx/osx.spec | 2 + electrum/gui/qt/__init__.py | 12 +--- electrum/gui/qt/address_dialog.py | 2 +- electrum/gui/qt/exception_window.py | 4 +- electrum/gui/qt/history_list.py | 6 +- electrum/gui/qt/installwizard.py | 5 +- electrum/gui/qt/invoice_list.py | 2 +- electrum/gui/qt/main_window.py | 47 ++++++------- electrum/gui/qt/network_dialog.py | 2 +- electrum/gui/qt/password_dialog.py | 14 ++-- electrum/gui/qt/qrtextedit.py | 6 +- electrum/gui/qt/request_list.py | 6 +- electrum/gui/qt/seed_dialog.py | 3 +- electrum/gui/qt/transaction_dialog.py | 2 +- electrum/gui/qt/util.py | 30 ++++---- electrum/plugins/audio_modem/qt.py | 8 +-- electrum/plugins/coldcard/qt.py | 4 +- electrum/plugins/digitalbitbox/qt.py | 4 +- electrum/plugins/hw_wallet/qt.py | 6 +- electrum/plugins/keepkey/qt.py | 4 +- electrum/plugins/ledger/qt.py | 4 +- electrum/plugins/revealer/qt.py | 10 +-- electrum/plugins/safe_t/qt.py | 4 +- electrum/plugins/trezor/qt.py | 4 +- electrum/plugins/trustedcoin/qt.py | 4 +- electrum/plugins/trustedcoin/trustedcoin.py | 4 +- electrum/util.py | 9 +++ icons.qrc | 70 ------------------- setup.py | 20 ------ 38 files changed, 109 insertions(+), 216 deletions(-) delete mode 160000 contrib/deterministic-build/electrum-icons delete mode 100644 icons.qrc diff --git a/.gitignore b/.gitignore index 07865821..f0e9ad03 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ bin/ .idea # icons -electrum/gui/qt/icons_rc.py electrum/gui/kivy/theming/light-0.png electrum/gui/kivy/theming/light.atlas diff --git a/.gitmodules b/.gitmodules index f95b1ebc..0803e6b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "contrib/deterministic-build/electrum-icons"] - path = contrib/deterministic-build/electrum-icons - url = https://github.com/spesmilo/electrum-icons [submodule "contrib/deterministic-build/electrum-locale"] path = contrib/deterministic-build/electrum-locale url = https://github.com/spesmilo/electrum-locale diff --git a/MANIFEST.in b/MANIFEST.in index 7caf98b0..34323d29 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include contrib/requirements/requirements.txt include contrib/requirements/requirements-hw.txt recursive-include packages *.py recursive-include packages cacert.pem -include icons.qrc graft icons graft electrum diff --git a/README.rst b/README.rst index ff3c18c1..9c7107ae 100644 --- a/README.rst +++ b/README.rst @@ -66,14 +66,6 @@ Run install (this should install dependencies):: python3 -m pip install .[fast] -Render the SVG icons to PNGs (optional):: - - for i in lock unlock confirmed status_lagging status_disconnected status_connected_proxy status_connected status_waiting preferences; do convert -background none icons/$i.svg icons/$i.png; done - -Compile the icons file for Qt:: - - sudo apt-get install pyqt5-dev-tools - pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py Compile the protobuf description file:: diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 7b44eeb9..7c7365a3 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -20,7 +20,7 @@ cd tmp pushd $WINEPREFIX/drive_c/electrum -# Load electrum-icons and electrum-locale for this release +# Load electrum-locale for this release git submodule init git submodule update @@ -43,7 +43,6 @@ find -exec touch -d '2000-11-11T11:11:11+00:00' {} + popd cp $WINEPREFIX/drive_c/electrum/LICENCE . -cp $WINEPREFIX/drive_c/electrum/contrib/deterministic-build/electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/electrum/gui/qt/ # Install frozen dependencies $PYTHON -m pip install -r ../../deterministic-build/requirements.txt diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 2c461dd9..6445f513 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -42,6 +42,8 @@ datas = [ (home+'electrum/locale', 'electrum/locale'), (home+'electrum/plugins', 'electrum/plugins'), ('C:\\Program Files (x86)\\ZBar\\bin\\', '.'), + (home+'icons/*.png', 'icons'), + (home+'icons/*.svg', 'icons'), ] datas += collect_data_files('trezorlib') datas += collect_data_files('safetlib') diff --git a/contrib/deterministic-build/check_submodules.sh b/contrib/deterministic-build/check_submodules.sh index d9c1b61d..c8df3a10 100755 --- a/contrib/deterministic-build/check_submodules.sh +++ b/contrib/deterministic-build/check_submodules.sh @@ -18,13 +18,6 @@ function get_git_mtime { fail=0 -for f in icons/* "icons.qrc"; do - if (( $(get_git_mtime "$f") > $(get_git_mtime "contrib/deterministic-build/electrum-icons/") )); then - echo "Modification time of $f (" $(get_git_mtime --readable "$f") ") is newer than"\ - "last update of electrum-icons" - fail=1 - fi -done if [ $(date +%s -d "2 weeks ago") -gt $(get_git_mtime "contrib/deterministic-build/electrum-locale/") ]; then echo "Last update from electrum-locale is older than 2 weeks."\ diff --git a/contrib/deterministic-build/electrum-icons b/contrib/deterministic-build/electrum-icons deleted file mode 160000 index 201d45cd..00000000 --- a/contrib/deterministic-build/electrum-icons +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 201d45cd5d855c4f9de5680ab5c53621574dc6b6 diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx index b33a3462..3ba96e7f 100755 --- a/contrib/osx/make_osx +++ b/contrib/osx/make_osx @@ -66,7 +66,6 @@ rm -rf $BUILDDIR > /dev/null 2>&1 mkdir $BUILDDIR cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./electrum/locale/ -cp ./contrib/deterministic-build/electrum-icons/icons_rc.py ./electrum/gui/qt/ info "Downloading libusb..." diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec index c86ee58e..87770397 100644 --- a/contrib/osx/osx.spec +++ b/contrib/osx/osx.spec @@ -76,6 +76,8 @@ datas = [ (electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'), (electrum + PYPKG + '/locale', PYPKG + '/locale'), (electrum + PYPKG + '/plugins', PYPKG + '/plugins'), + (electrum + 'icons/*.png', 'icons'), + (electrum + 'icons/*.svg', 'icons'), ] datas += collect_data_files('trezorlib') datas += collect_data_files('safetlib') diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index a5fdb78b..bf770525 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -49,14 +49,6 @@ from electrum.util import (UserCancelled, PrintError, profiler, from .installwizard import InstallWizard -try: - from . import icons_rc -except Exception as e: - print(e) - print("Error: Could not find icons file.") - print("Please run 'pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py'") - sys.exit(1) - from .util import * # * needed for plugins from .main_window import ElectrumWindow from .network_dialog import NetworkDialog @@ -157,9 +149,9 @@ class ElectrumGui(PrintError): def tray_icon(self): if self.dark_icon: - return QIcon(':icons/electrum_dark_icon.png') + return read_QIcon('electrum_dark_icon.png') else: - return QIcon(':icons/electrum_light_icon.png') + return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): self.dark_icon = not self.dark_icon diff --git a/electrum/gui/qt/address_dialog.py b/electrum/gui/qt/address_dialog.py index 25d9dbf8..8dfb220a 100644 --- a/electrum/gui/qt/address_dialog.py +++ b/electrum/gui/qt/address_dialog.py @@ -59,7 +59,7 @@ class AddressDialog(WindowModalDialog): vbox.addWidget(QLabel(_("Address:"))) self.addr_e = ButtonsLineEdit(self.address) self.addr_e.addCopyButton(self.app) - icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png" + icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" self.addr_e.addButton(icon, self.show_qr, _("Show QR Code")) self.addr_e.setReadOnly(True) vbox.addWidget(self.addr_e) diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py index fb81e227..3d3d650a 100644 --- a/electrum/gui/qt/exception_window.py +++ b/electrum/gui/qt/exception_window.py @@ -32,7 +32,7 @@ from PyQt5.QtWidgets import * from electrum.i18n import _ from electrum.base_crash_reporter import BaseCrashReporter -from .util import MessageBoxMixin +from .util import MessageBoxMixin, read_QIcon class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin): @@ -74,7 +74,7 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin): report_button = QPushButton(_('Send Bug Report')) report_button.clicked.connect(self.send_report) - report_button.setIcon(QIcon(":icons/tab_send.png")) + report_button.setIcon(read_QIcon("tab_send.png")) buttons.addWidget(report_button) never_button = QPushButton(_('Never')) diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index acf610e7..ee691a42 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -149,7 +149,7 @@ class HistoryModel(QAbstractItemModel, PrintError): return QVariant(d[col]) if role not in (Qt.DisplayRole, Qt.EditRole): if col == HistoryColumns.STATUS_ICON and role == Qt.DecorationRole: - return QVariant(self.view.icon_cache.get(":icons/" + TX_ICONS[status])) + return QVariant(read_QIcon(TX_ICONS[status])) elif col == HistoryColumns.STATUS_ICON and role == Qt.ToolTipRole: return QVariant(str(conf) + _(" confirmation" + ("s" if conf != 1 else ""))) elif col > HistoryColumns.DESCRIPTION and role == Qt.TextAlignmentRole: @@ -159,7 +159,7 @@ class HistoryModel(QAbstractItemModel, PrintError): return QVariant(monospace_font) elif col == HistoryColumns.DESCRIPTION and role == Qt.DecorationRole \ and self.parent.wallet.invoices.paid.get(tx_hash): - return QVariant(self.view.icon_cache.get(":icons/seal")) + return QVariant(read_QIcon("seal")) elif col in (HistoryColumns.DESCRIPTION, HistoryColumns.COIN_VALUE) \ and role == Qt.ForegroundRole and tx_item['value'].value < 0: red_brush = QBrush(QColor("#BC1E1E")) @@ -584,7 +584,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): if child_tx: menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx)) if pr_key: - menu.addAction(self.icon_cache.get(":icons/seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key)) + menu.addAction(read_QIcon("seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key)) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position)) diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py index ffd18867..102721a8 100644 --- a/electrum/gui/qt/installwizard.py +++ b/electrum/gui/qt/installwizard.py @@ -154,7 +154,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) - self.set_icon(':icons/electrum.png') + self.set_icon('electrum.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. @@ -329,7 +329,8 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename - self.logo.setPixmap(QPixmap(filename).scaledToWidth(60, mode=Qt.SmoothTransformation)) + self.logo.setPixmap(QPixmap(icon_path(filename)) + .scaledToWidth(60, mode=Qt.SmoothTransformation)) return prior_filename def set_layout(self, layout, title=None, next_enabled=True): diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py index 6f863bf0..b82d95c4 100644 --- a/electrum/gui/qt/invoice_list.py +++ b/electrum/gui/qt/invoice_list.py @@ -53,7 +53,7 @@ class InvoiceList(MyTreeView): labels = [date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')] items = [QStandardItem(e) for e in labels] self.set_editability(items) - items[4].setIcon(self.icon_cache.get(pr_icons.get(status))) + items[4].setIcon(read_QIcon(pr_icons.get(status))) items[0].setData(key, role=Qt.UserRole) items[1].setFont(QFont(MONOSPACE_FONT)) items[3].setFont(QFont(MONOSPACE_FONT)) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 0c61250b..340411e2 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -161,9 +161,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.utxo_tab = self.create_utxo_tab() self.console_tab = self.create_console_tab() self.contacts_tab = self.create_contacts_tab() - tabs.addTab(self.create_history_tab(), QIcon(":icons/tab_history.png"), _('History')) - tabs.addTab(self.send_tab, QIcon(":icons/tab_send.png"), _('Send')) - tabs.addTab(self.receive_tab, QIcon(":icons/tab_receive.png"), _('Receive')) + tabs.addTab(self.create_history_tab(), read_QIcon("tab_history.png"), _('History')) + tabs.addTab(self.send_tab, read_QIcon("tab_send.png"), _('Send')) + tabs.addTab(self.receive_tab, read_QIcon("tab_receive.png"), _('Receive')) def add_optional_tab(tabs, tab, icon, description, name): tab.tab_icon = icon @@ -173,10 +173,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if self.config.get('show_{}_tab'.format(name), False): tabs.addTab(tab, icon, description.replace("&", "")) - add_optional_tab(tabs, self.addresses_tab, QIcon(":icons/tab_addresses.png"), _("&Addresses"), "addresses") - add_optional_tab(tabs, self.utxo_tab, QIcon(":icons/tab_coins.png"), _("Co&ins"), "utxo") - add_optional_tab(tabs, self.contacts_tab, QIcon(":icons/tab_contacts.png"), _("Con&tacts"), "contacts") - add_optional_tab(tabs, self.console_tab, QIcon(":icons/tab_console.png"), _("Con&sole"), "console") + add_optional_tab(tabs, self.addresses_tab, read_QIcon("tab_addresses.png"), _("&Addresses"), "addresses") + add_optional_tab(tabs, self.utxo_tab, read_QIcon("tab_coins.png"), _("Co&ins"), "utxo") + add_optional_tab(tabs, self.contacts_tab, read_QIcon("tab_contacts.png"), _("Con&tacts"), "contacts") + add_optional_tab(tabs, self.console_tab, read_QIcon("tab_console.png"), _("Con&sole"), "console") tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setCentralWidget(tabs) @@ -184,7 +184,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if self.config.get("is_maximized"): self.showMaximized() - self.setWindowIcon(QIcon(":icons/electrum.png")) + self.setWindowIcon(read_QIcon("electrum.png")) self.init_menubar() wrtabs = weakref.proxy(tabs) @@ -675,7 +675,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if self.tray: try: # this requires Qt 5.9 - self.tray.showMessage("Electrum", message, QIcon(":icons/electrum_dark_icon"), 20000) + self.tray.showMessage("Electrum", message, read_QIcon("electrum_dark_icon"), 20000) except TypeError: self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000) @@ -773,7 +773,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if self.network is None: text = _("Offline") - icon = QIcon(":icons/status_disconnected.png") + icon = read_QIcon("status_disconnected.png") elif self.network.is_connected(): server_height = self.network.get_server_height() @@ -784,10 +784,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): # Display the synchronizing message in that case. if not self.wallet.up_to_date or server_height == 0: text = _("Synchronizing...") - icon = QIcon(":icons/status_waiting.png") + icon = read_QIcon("status_waiting.png") elif server_lag > 1: text = _("Server is lagging ({} blocks)").format(server_lag) - icon = QIcon(":icons/status_lagging%s.png"%fork_str) + icon = read_QIcon("status_lagging%s.png"%fork_str) else: c, u, x = self.wallet.get_balance() text = _("Balance" ) + ": %s "%(self.format_amount_and_units(c)) @@ -801,15 +801,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): text += self.fx.get_fiat_status_text(c + u + x, self.base_unit(), self.get_decimal_point()) or '' if not self.network.proxy: - icon = QIcon(":icons/status_connected%s.png"%fork_str) + icon = read_QIcon("status_connected%s.png"%fork_str) else: - icon = QIcon(":icons/status_connected_proxy%s.png"%fork_str) + icon = read_QIcon("status_connected_proxy%s.png"%fork_str) else: if self.network.proxy: text = "{} ({})".format(_("Not connected"), _("proxy enabled")) else: text = _("Not connected") - icon = QIcon(":icons/status_disconnected.png") + icon = read_QIcon("status_disconnected.png") self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename())) self.balance_label.setText(text) @@ -1237,7 +1237,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): _('Also, when batching RBF transactions, BIP 125 imposes a lower bound on the fee.')) QMessageBox.information(self, 'Fee rounding', text) - self.feerounding_icon = QPushButton(QIcon(':icons/info.png'), '') + self.feerounding_icon = QPushButton(read_QIcon('info.png'), '') self.feerounding_icon.setFixedWidth(20) self.feerounding_icon.setFlat(True) self.feerounding_icon.clicked.connect(feerounding_onclick) @@ -2038,24 +2038,23 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.update_check_button = QPushButton("") self.update_check_button.setFlat(True) self.update_check_button.setCursor(QCursor(Qt.PointingHandCursor)) - self.update_check_button.setIcon(QIcon(":icons/update.png")) + self.update_check_button.setIcon(read_QIcon("update.png")) self.update_check_button.hide() sb.addPermanentWidget(self.update_check_button) - self.lock_icon = QIcon() - self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog ) + self.password_button = StatusBarButton(QIcon(), _("Password"), self.change_password_dialog ) sb.addPermanentWidget(self.password_button) - sb.addPermanentWidget(StatusBarButton(QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) ) - self.seed_button = StatusBarButton(QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) + sb.addPermanentWidget(StatusBarButton(read_QIcon("preferences.png"), _("Preferences"), self.settings_dialog ) ) + self.seed_button = StatusBarButton(read_QIcon("seed.png"), _("Seed"), self.show_seed_dialog ) sb.addPermanentWidget(self.seed_button) - self.status_button = StatusBarButton(QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self)) + self.status_button = StatusBarButton(read_QIcon("status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self)) sb.addPermanentWidget(self.status_button) run_hook('create_status_bar', sb) self.setStatusBar(sb) def update_lock_icon(self): - icon = QIcon(":icons/lock.png") if self.wallet.has_password() else QIcon(":icons/unlock.png") + icon = read_QIcon("lock.png") if self.wallet.has_password() else read_QIcon("unlock.png") self.password_button.setIcon(icon) def update_buttons_on_seed(self): @@ -3356,5 +3355,5 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): msg = (_("Transaction added to wallet history.") + '\n\n' + _("Note: this is an offline transaction, if you want the network " "to see it, you need to broadcast it.")) - win.msg_box(QPixmap(":icons/offline_tx.png"), None, _('Success'), msg) + win.msg_box(QPixmap(icon_path("offline_tx.png")), None, _('Success'), msg) return True diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 3cbdab41..43a4e1de 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -270,7 +270,7 @@ class NetworkChoiceLayout(object): self.proxy_password.textEdited.connect(self.proxy_settings_changed) self.tor_cb = QCheckBox(_("Use Tor Proxy")) - self.tor_cb.setIcon(QIcon(":icons/tor_logo.png")) + self.tor_cb.setIcon(read_QIcon("tor_logo.png")) self.tor_cb.hide() self.tor_cb.clicked.connect(self.use_tor_proxy) diff --git a/electrum/gui/qt/password_dialog.py b/electrum/gui/qt/password_dialog.py index 3202618e..83862b92 100644 --- a/electrum/gui/qt/password_dialog.py +++ b/electrum/gui/qt/password_dialog.py @@ -103,10 +103,11 @@ class PasswordLayout(object): if wallet and wallet.has_password(): grid.addWidget(QLabel(_('Current Password:')), 0, 0) grid.addWidget(self.pw, 0, 1) - lockfile = ":icons/lock.png" + lockfile = "lock.png" else: - lockfile = ":icons/unlock.png" - logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation)) + lockfile = "unlock.png" + logo.setPixmap(QPixmap(icon_path(lockfile)) + .scaledToWidth(36, mode=Qt.SmoothTransformation)) grid.addWidget(QLabel(msgs[0]), 1, 0) grid.addWidget(self.new_pw, 1, 1) @@ -195,10 +196,11 @@ class PasswordLayoutForHW(object): vbox.addLayout(logo_grid) if wallet and wallet.has_storage_encryption(): - lockfile = ":icons/lock.png" + lockfile = "lock.png" else: - lockfile = ":icons/unlock.png" - logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation)) + lockfile = "unlock.png" + logo.setPixmap(QPixmap(icon_path(lockfile)) + .scaledToWidth(36, mode=Qt.SmoothTransformation)) vbox.addLayout(grid) diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py index 53cb4ff4..b84ea5d6 100644 --- a/electrum/gui/qt/qrtextedit.py +++ b/electrum/gui/qt/qrtextedit.py @@ -13,7 +13,7 @@ class ShowQRTextEdit(ButtonsTextEdit): def __init__(self, text=None): ButtonsTextEdit.__init__(self, text) self.setReadOnly(1) - icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png" + icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" self.addButton(icon, self.qr_show, _("Show as QR code")) run_hook('show_text_edit', self) @@ -38,8 +38,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): ButtonsTextEdit.__init__(self, text) self.allow_multi = allow_multi self.setReadOnly(0) - self.addButton(":icons/file.png", self.file_input, _("Read file")) - icon = ":icons/camera_white.png" if ColorScheme.dark_scheme else ":icons/camera_dark.png" + self.addButton("file.png", self.file_input, _("Read file")) + icon = "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png" self.addButton(icon, self.qr_input, _("Read QR code")) run_hook('scan_text_edit', self) diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py index a21acd97..9a725724 100644 --- a/electrum/gui/qt/request_list.py +++ b/electrum/gui/qt/request_list.py @@ -33,7 +33,7 @@ from electrum.plugin import run_hook from electrum.paymentrequest import PR_UNKNOWN from electrum.wallet import InternalAddressCorruption -from .util import MyTreeView, pr_tooltips, pr_icons +from .util import MyTreeView, pr_tooltips, pr_icons, read_QIcon class RequestList(MyTreeView): filter_columns = [0, 1, 2, 3, 4] # Date, Account, Address, Description, Amount @@ -108,10 +108,10 @@ class RequestList(MyTreeView): items = [QStandardItem(e) for e in labels] self.set_editability(items) if signature is not None: - items[2].setIcon(self.icon_cache.get(":icons/seal.png")) + items[2].setIcon(read_QIcon("seal.png")) items[2].setToolTip('signed by '+ requestor) if status is not PR_UNKNOWN: - items[5].setIcon(self.icon_cache.get(pr_icons.get(status))) + items[5].setIcon(read_QIcon(pr_icons.get(status))) items[3].setData(address, Qt.UserRole) self.model().insertRow(self.model().rowCount(), items) diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index bca282b0..da3f332e 100644 --- a/electrum/gui/qt/seed_dialog.py +++ b/electrum/gui/qt/seed_dialog.py @@ -110,7 +110,8 @@ class SeedLayout(QVBoxLayout): hbox = QHBoxLayout() if icon: logo = QLabel() - logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(64, mode=Qt.SmoothTransformation)) + logo.setPixmap(QPixmap(icon_path("seed.png")) + .scaledToWidth(64, mode=Qt.SmoothTransformation)) logo.setMaximumWidth(60) hbox.addWidget(logo) hbox.addWidget(self.seed_e) diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index 5e3836d3..c12a680d 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -98,7 +98,7 @@ class TxDialog(QDialog, MessageBoxMixin): vbox.addWidget(QLabel(_("Transaction ID:"))) self.tx_hash_e = ButtonsLineEdit() qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self) - qr_icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png" + qr_icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" self.tx_hash_e.addButton(qr_icon, qr_show, _("Show as QR code")) self.tx_hash_e.setReadOnly(True) vbox.addWidget(self.tx_hash_e) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index aee7bc1b..88fe6fe7 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -6,7 +6,7 @@ import platform import queue import traceback from distutils.version import StrictVersion -from functools import partial +from functools import partial, lru_cache from typing import NamedTuple, Callable, Optional, TYPE_CHECKING import base64 @@ -18,7 +18,8 @@ from electrum import version from electrum import ecc from electrum import constants from electrum.i18n import _, languages -from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, PrintError +from electrum.util import (FileImportFailed, FileExportFailed, make_aiohttp_session, + PrintError, resource_path) from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED if TYPE_CHECKING: @@ -36,9 +37,9 @@ else: dialogs = [] pr_icons = { - PR_UNPAID:":icons/unpaid.png", - PR_PAID:":icons/confirmed.png", - PR_EXPIRED:":icons/expired.png" + PR_UNPAID:"unpaid.png", + PR_PAID:"confirmed.png", + PR_EXPIRED:"expired.png" } pr_tooltips = { @@ -429,8 +430,6 @@ class MyTreeView(QTreeView): self.customContextMenuRequested.connect(create_menu) self.setUniformRowHeights(True) - self.icon_cache = IconCache() - # Control which columns are editable if editable_columns is None: editable_columns = {stretch_column} @@ -599,7 +598,7 @@ class ButtonsWidget(QWidget): def addButton(self, icon_name, on_click, tooltip): button = QToolButton(self) - button.setIcon(QIcon(icon_name)) + button.setIcon(read_QIcon(icon_name)) button.setIconSize(QSize(25,25)) button.setStyleSheet("QToolButton { border: none; hover {border: 1px} pressed {border: 1px} padding: 0px; }") button.setVisible(True) @@ -610,7 +609,7 @@ class ButtonsWidget(QWidget): def addCopyButton(self, app): self.app = app - self.addButton(":icons/copy.png", self.on_copy, _("Copy to clipboard")) + self.addButton("copy.png", self.on_copy, _("Copy to clipboard")) def on_copy(self): self.app.clipboard().setText(self.text()) @@ -795,15 +794,14 @@ def get_parent_main_window(widget): return widget return None -class IconCache: - def __init__(self): - self.__cache = {} +def icon_path(icon_basename): + return resource_path('icons', icon_basename) - def get(self, file_name): - if file_name not in self.__cache: - self.__cache[file_name] = QIcon(file_name) - return self.__cache[file_name] + +@lru_cache(maxsize=1000) +def read_QIcon(icon_basename): + return QIcon(icon_path(icon_basename)) def get_default_language(): diff --git a/electrum/plugins/audio_modem/qt.py b/electrum/plugins/audio_modem/qt.py index 2b88df88..36ba100e 100644 --- a/electrum/plugins/audio_modem/qt.py +++ b/electrum/plugins/audio_modem/qt.py @@ -6,7 +6,7 @@ import sys import platform from electrum.plugin import BasePlugin, hook -from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog +from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon from electrum.util import print_msg, print_error from electrum.i18n import _ @@ -71,7 +71,7 @@ class Plugin(BasePlugin): @hook def transaction_dialog(self, dialog): b = QPushButton() - b.setIcon(QIcon(":icons/speaker.png")) + b.setIcon(read_QIcon("speaker.png")) def handler(): blob = json.dumps(dialog.tx.as_dict()) @@ -81,7 +81,7 @@ class Plugin(BasePlugin): @hook def scan_text_edit(self, parent): - parent.addButton(':icons/microphone.png', partial(self._recv, parent), + parent.addButton('microphone.png', partial(self._recv, parent), _("Read from microphone")) @hook @@ -89,7 +89,7 @@ class Plugin(BasePlugin): def handler(): blob = str(parent.toPlainText()) self._send(parent=parent, blob=blob) - parent.addButton(':icons/speaker.png', handler, _("Send to speaker")) + parent.addButton('speaker.png', handler, _("Send to speaker")) def _audio_interface(self): interface = amodem.audio.Interface(config=self.modem_config) diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py index b7fd0e33..a3409822 100644 --- a/electrum/plugins/coldcard/qt.py +++ b/electrum/plugins/coldcard/qt.py @@ -11,8 +11,8 @@ from ..hw_wallet.plugin import only_hook_if_libraries_available class Plugin(ColdcardPlugin, QtPluginBase): - icon_unpaired = ":icons/coldcard_unpaired.png" - icon_paired = ":icons/coldcard.png" + icon_unpaired = "coldcard_unpaired.png" + icon_paired = "coldcard.png" def create_handler(self, window): return Coldcard_Handler(window) diff --git a/electrum/plugins/digitalbitbox/qt.py b/electrum/plugins/digitalbitbox/qt.py index 292ebbbe..804162ba 100644 --- a/electrum/plugins/digitalbitbox/qt.py +++ b/electrum/plugins/digitalbitbox/qt.py @@ -10,8 +10,8 @@ from .digitalbitbox import DigitalBitboxPlugin class Plugin(DigitalBitboxPlugin, QtPluginBase): - icon_unpaired = ":icons/digitalbitbox_unpaired.png" - icon_paired = ":icons/digitalbitbox.png" + icon_unpaired = "digitalbitbox_unpaired.png" + icon_paired = "digitalbitbox.png" def create_handler(self, window): return DigitalBitbox_Handler(window) diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index e30ad11c..a327b05e 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -73,8 +73,8 @@ class QtHandlerBase(QObject, PrintError): def _update_status(self, paired): if hasattr(self, 'button'): button = self.button - icon = button.icon_paired if paired else button.icon_unpaired - button.setIcon(QIcon(icon)) + icon_name = button.icon_paired if paired else button.icon_unpaired + button.setIcon(read_QIcon(icon_name)) def query_choice(self, msg, labels): self.done.clear() @@ -234,4 +234,4 @@ class QtPluginBase(object): def show_address(): addr = receive_address_e.text() keystore.thread.add(partial(plugin.show_address, wallet, addr, keystore)) - receive_address_e.addButton(":icons/eye1.png", show_address, _("Show on {}").format(plugin.device)) + receive_address_e.addButton("eye1.png", show_address, _("Show on {}").format(plugin.device)) diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index 47a03340..e5f5b6a4 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -295,8 +295,8 @@ class QtPlugin(QtPluginBase): class Plugin(KeepKeyPlugin, QtPlugin): - icon_paired = ":icons/keepkey.png" - icon_unpaired = ":icons/keepkey_unpaired.png" + icon_paired = "keepkey.png" + icon_unpaired = "keepkey_unpaired.png" @classmethod def pin_matrix_widget_class(self): diff --git a/electrum/plugins/ledger/qt.py b/electrum/plugins/ledger/qt.py index 6be64988..c7cd8b4d 100644 --- a/electrum/plugins/ledger/qt.py +++ b/electrum/plugins/ledger/qt.py @@ -11,8 +11,8 @@ from ..hw_wallet.plugin import only_hook_if_libraries_available class Plugin(LedgerPlugin, QtPluginBase): - icon_unpaired = ":icons/ledger_unpaired.png" - icon_paired = ":icons/ledger.png" + icon_unpaired = "ledger_unpaired.png" + icon_paired = "ledger.png" def create_handler(self, window): return Ledger_Handler(window) diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py index b11b5f5e..fb67dae2 100644 --- a/electrum/plugins/revealer/qt.py +++ b/electrum/plugins/revealer/qt.py @@ -54,7 +54,7 @@ class Plugin(RevealerPlugin): @hook def create_status_bar(self, parent): - b = StatusBarButton(QIcon(':icons/revealer.png'), "Revealer "+_("secret backup utility"), + b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("secret backup utility"), partial(self.setup_dialog, parent)) parent.addPermanentWidget(b) @@ -98,7 +98,7 @@ class Plugin(RevealerPlugin): vbox = QVBoxLayout() logo = QLabel() self.hbox.addWidget(logo) - logo.setPixmap(QPixmap(':icons/revealer.png')) + logo.setPixmap(QPixmap(icon_path('revealer.png'))) logo.setAlignment(Qt.AlignLeft) self.hbox.addSpacing(16) vbox.addWidget(WWLabel(""+_("Revealer Secret Backup Plugin")+"
" @@ -228,7 +228,7 @@ class Plugin(RevealerPlugin): self.vbox = QVBoxLayout() logo = QLabel() hbox.addWidget(logo) - logo.setPixmap(QPixmap(':icons/revealer.png')) + logo.setPixmap(QPixmap(icon_path('revealer.png'))) logo.setAlignment(Qt.AlignLeft) hbox.addSpacing(16) self.vbox.addWidget(WWLabel("" + _("Revealer Secret Backup Plugin") + "
" @@ -549,7 +549,7 @@ class Plugin(RevealerPlugin): painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11, - QImage(':icons/electrumb.png').scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation)) + QImage(icon_path('electrumb.png')).scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation)) painter.setPen(QPen(Qt.white, border_thick*8)) painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2, @@ -575,7 +575,7 @@ class Plugin(RevealerPlugin): painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) - logo = QImage(':icons/revealer_c.png').scaledToWidth(1.3*(total_distance_h)) + logo = QImage(icon_path('revealer_c.png')).scaledToWidth(1.3*(total_distance_h)) painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation) #frame around logo diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index 40e81fb1..88931bea 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -169,8 +169,8 @@ class QtPlugin(QtPluginBase): class Plugin(SafeTPlugin, QtPlugin): - icon_unpaired = ":icons/safe-t_unpaired.png" - icon_paired = ":icons/safe-t.png" + icon_unpaired = "safe-t_unpaired.png" + icon_paired = "safe-t.png" @classmethod def pin_matrix_widget_class(self): diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index 44745871..b45d8325 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -259,8 +259,8 @@ class QtPlugin(QtPluginBase): class Plugin(TrezorPlugin, QtPlugin): - icon_unpaired = ":icons/trezor_unpaired.png" - icon_paired = ":icons/trezor.png" + icon_unpaired = "trezor_unpaired.png" + icon_paired = "trezor.png" @classmethod def pin_matrix_widget_class(self): diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py index c84a2bbb..71db486a 100644 --- a/electrum/plugins/trustedcoin/qt.py +++ b/electrum/plugins/trustedcoin/qt.py @@ -90,7 +90,7 @@ class Plugin(TrustedCoinPlugin): action = lambda: window.show_message(msg) else: action = partial(self.settings_dialog, window) - button = StatusBarButton(QIcon(":icons/trustedcoin-status.png"), + button = StatusBarButton(read_QIcon("trustedcoin-status.png"), _("TrustedCoin"), action) window.statusBar().addPermanentWidget(button) self.start_request_thread(window.wallet) @@ -152,7 +152,7 @@ class Plugin(TrustedCoinPlugin): hbox = QHBoxLayout() logo = QLabel() - logo.setPixmap(QPixmap(":icons/trustedcoin-status.png")) + logo.setPixmap(QPixmap(icon_path("trustedcoin-status.png"))) msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '
'\ + _("For more information, visit") + " https://api.trustedcoin.com/#/electrum-help" label = QLabel(msg) diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py index c1220867..f8484d29 100644 --- a/electrum/plugins/trustedcoin/trustedcoin.py +++ b/electrum/plugins/trustedcoin/trustedcoin.py @@ -492,7 +492,7 @@ class TrustedCoinPlugin(BasePlugin): window.wallet.is_billing = False def show_disclaimer(self, wizard): - wizard.set_icon(':icons/trustedcoin-wizard.png') + wizard.set_icon('trustedcoin-wizard.png') wizard.stack = [] wizard.confirm_dialog(title='Disclaimer', message='\n\n'.join(self.disclaimer_msg), run_next = lambda x: wizard.run('choose_seed')) @@ -581,7 +581,7 @@ class TrustedCoinPlugin(BasePlugin): wizard.passphrase_dialog(run_next=f) if is_ext else f('') def restore_choice(self, wizard, seed, passphrase): - wizard.set_icon(':icons/trustedcoin-wizard.png') + wizard.set_icon('trustedcoin-wizard.png') wizard.stack = [] title = _('Restore 2FA wallet') msg = ' '.join([ diff --git a/electrum/util.py b/electrum/util.py index e4b556f8..4154a4c7 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -488,6 +488,15 @@ def user_dir(): #raise Exception("No home directory found in environment variables.") return + +def resource_path(*parts): + return os.path.join(base_dir, *parts) + + +# absolute path to project root dir when running from source +base_dir = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + + def is_valid_email(s): regexp = r"[^@]+@[^@]+\.[^@]+" return re.match(regexp, s) is not None diff --git a/icons.qrc b/icons.qrc deleted file mode 100644 index ab4318dc..00000000 --- a/icons.qrc +++ /dev/null @@ -1,70 +0,0 @@ - - - icons/electrum.png - icons/clock1.png - icons/clock2.png - icons/clock3.png - icons/clock4.png - icons/clock5.png - icons/confirmed.png - icons/copy.png - icons/digitalbitbox.png - icons/digitalbitbox_unpaired.png - icons/expired.png - icons/electrum_light_icon.png - icons/electrum_dark_icon.png - icons/electrumb.png - icons/eye1.png - icons/file.png - icons/info.png - icons/keepkey.png - icons/keepkey_unpaired.png - icons/key.png - icons/ledger.png - icons/ledger_unpaired.png - icons/lock.png - icons/microphone.png - icons/network.png - icons/offline_tx.png - icons/revealer.png - icons/revealer_c.png - icons/qrcode.png - icons/qrcode_white.png - icons/camera_dark.png - icons/camera_white.png - icons/preferences.png - icons/safe-t_unpaired.png - icons/safe-t.png - icons/seed.png - icons/status_connected.png - icons/status_connected_fork.png - icons/status_connected_proxy.png - icons/status_connected_proxy_fork.png - icons/status_disconnected.png - icons/status_waiting.png - icons/status_lagging.png - icons/status_lagging_fork.png - icons/seal.png - icons/tab_addresses.png - icons/tab_coins.png - icons/tab_console.png - icons/tab_contacts.png - icons/tab_history.png - icons/tab_receive.png - icons/tab_send.png - icons/tor_logo.png - icons/speaker.png - icons/trezor_unpaired.png - icons/trezor.png - icons/coldcard.png - icons/coldcard_unpaired.png - icons/trustedcoin-status.png - icons/trustedcoin-wizard.png - icons/unconfirmed.png - icons/unpaid.png - icons/unlock.png - icons/update.png - icons/warning.png - icons/zoom.png - - diff --git a/setup.py b/setup.py index 41c3d9f9..10404550 100755 --- a/setup.py +++ b/setup.py @@ -58,23 +58,6 @@ extras_require = { extras_require['full'] = [pkg for sublist in list(extras_require.values()) for pkg in sublist] -class CustomInstallCommand(install): - def run(self): - install.run(self) - # potentially build Qt icons file - try: - import PyQt5 - except ImportError: - pass - else: - try: - path = os.path.join(self.install_lib, "electrum/gui/qt/icons_rc.py") - if not os.path.exists(path): - subprocess.call(["pyrcc5", "icons.qrc", "-o", path]) - except Exception as e: - print('Warning: building icons file failed with {}'.format(repr(e))) - - setup( name="Electrum", version=version.ELECTRUM_VERSION, @@ -105,7 +88,4 @@ setup( license="MIT Licence", url="https://electrum.org", long_description="""Lightweight Bitcoin Wallet""", - cmdclass={ - 'install': CustomInstallCommand, - }, ) From 7266ecc2b80a56ab52b26dfecd83dfea5124d105 Mon Sep 17 00:00:00 2001 From: ghost43 Date: Fri, 1 Feb 2019 21:20:45 +0100 Subject: [PATCH 12/78] contrib/make_tgz: small improvements. (#5040) --- contrib/make_tgz | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/contrib/make_tgz b/contrib/make_tgz index 6e5e173a..5110cef3 100755 --- a/contrib/make_tgz +++ b/contrib/make_tgz @@ -1,11 +1,31 @@ #!/bin/bash -contrib=$(dirname "$0") -packages="$contrib"/../packages/ +set -e -if [ ! -d "$packages" ]; then - echo "Run make_packages first!" - exit 1 -fi +CONTRIB="$(dirname "$(readlink -e "$0")")" +ROOT_FOLDER="$CONTRIB"/.. +PACKAGES="$ROOT_FOLDER"/packages/ +LOCALE="$ROOT_FOLDER"/electrum/locale/ -python3 setup.py sdist --format=zip,gztar +( + cd "$ROOT_FOLDER" + + if [ ! -d "$LOCALE" ]; then + echo "Run make_locale first!" + exit 1 + fi + + if [ ! -d "$PACKAGES" ]; then + echo "Run make_packages first!" + exit 1 + fi + + echo "'git clean -fx' would delete the following files: >>>" + git clean -fx --dry-run + echo "<<<" + + # we could build the kivy atlas potentially? + #(cd electrum/gui/kivy/; make theming) || echo "building kivy atlas failed! skipping." + + python3 setup.py --quiet sdist --format=zip,gztar +) From 7ea01e9e913196790ceb1d814253aab57bdb9df4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 1 Feb 2019 21:57:18 +0100 Subject: [PATCH 13/78] qt inline icons: change mouse-over cursor --- electrum/gui/qt/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 88fe6fe7..6aaa65b6 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -600,6 +600,7 @@ class ButtonsWidget(QWidget): button = QToolButton(self) button.setIcon(read_QIcon(icon_name)) button.setIconSize(QSize(25,25)) + button.setCursor(QCursor(Qt.PointingHandCursor)) button.setStyleSheet("QToolButton { border: none; hover {border: 1px} pressed {border: 1px} padding: 0px; }") button.setVisible(True) button.setToolTip(tooltip) From 4fa87d8595fa02f8d7503da27eef19a03cb365fb Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 1 Feb 2019 23:32:24 +0100 Subject: [PATCH 14/78] fix: qt icons not available when installed as python package follow-up #5053 --- electrum/gui/icons | 1 + electrum/gui/qt/util.py | 2 +- electrum/util.py | 6 +++--- setup.py | 5 ++++- 4 files changed, 9 insertions(+), 5 deletions(-) create mode 120000 electrum/gui/icons diff --git a/electrum/gui/icons b/electrum/gui/icons new file mode 120000 index 00000000..9a5906b7 --- /dev/null +++ b/electrum/gui/icons @@ -0,0 +1 @@ +../../icons/ \ No newline at end of file diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 6aaa65b6..215cdc6d 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -797,7 +797,7 @@ def get_parent_main_window(widget): def icon_path(icon_basename): - return resource_path('icons', icon_basename) + return resource_path('gui', 'icons', icon_basename) @lru_cache(maxsize=1000) diff --git a/electrum/util.py b/electrum/util.py index 4154a4c7..f04f0c80 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -490,11 +490,11 @@ def user_dir(): def resource_path(*parts): - return os.path.join(base_dir, *parts) + return os.path.join(pkg_dir, *parts) -# absolute path to project root dir when running from source -base_dir = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] +# absolute path to python package folder of electrum ("lib") +pkg_dir = os.path.split(os.path.realpath(__file__))[0] def is_valid_email(s): diff --git a/setup.py b/setup.py index 10404550..f39a0471 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']: usr_share = os.path.expanduser('~/.local/share') data_files += [ (os.path.join(usr_share, 'applications/'), ['electrum.desktop']), - (os.path.join(usr_share, icons_dirname), ['icons/electrum.png']) + (os.path.join(usr_share, icons_dirname), ['icons/electrum.png']), ] extras_require = { @@ -79,6 +79,9 @@ setup( 'wordlist/*.txt', 'locale/*/LC_MESSAGES/electrum.mo', ], + 'electrum.gui': [ + 'icons/*', + ], }, scripts=['electrum/electrum'], data_files=data_files, From add3b36f32a12ae2aee75f80ca89c37065fa03af Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 2 Feb 2019 08:07:48 +0100 Subject: [PATCH 15/78] build: replace remaining "python setup.py install" with "pip install" --- contrib/build-wine/build-electrum-git.sh | 2 +- contrib/osx/make_osx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 7c7365a3..1dcf6f46 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -50,7 +50,7 @@ $PYTHON -m pip install -r ../../deterministic-build/requirements.txt $PYTHON -m pip install -r ../../deterministic-build/requirements-hw.txt pushd $WINEPREFIX/drive_c/electrum -$PYTHON setup.py install +$PYTHON -m pip install . popd cd .. diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx index 3ba96e7f..6e6460fe 100755 --- a/contrib/osx/make_osx +++ b/contrib/osx/make_osx @@ -107,7 +107,7 @@ python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --u fail "Could not install hardware wallet requirements" info "Building $PACKAGE..." -python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE" +python3 -m pip install --user . > /dev/null || fail "Could not build $PACKAGE" info "Faking timestamps..." for d in ~/Library/Python/ ~/.pyenv .; do From 52d602b6c1e510470aa004567e366c0c86503820 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 2 Feb 2019 20:10:12 +0100 Subject: [PATCH 16/78] network: fix get_servers to not modify default list --- electrum/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/network.py b/electrum/network.py index 883a38b8..bd43a100 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -438,7 +438,7 @@ class Network(PrintError): @with_recent_servers_lock def get_servers(self): # start with hardcoded servers - out = constants.net.DEFAULT_SERVERS + out = dict(constants.net.DEFAULT_SERVERS) # copy # add recent servers for s in self.recent_servers: try: From 905e271db9db322bc4edcf3b21408af32cc82c32 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 3 Feb 2019 12:28:59 +0100 Subject: [PATCH 17/78] remove phishing server --- electrum/servers.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/electrum/servers.json b/electrum/servers.json index 2677b814..f7f4e5a6 100644 --- a/electrum/servers.json +++ b/electrum/servers.json @@ -317,12 +317,6 @@ "t": "50001", "version": "1.4" }, - "oneweek.duckdns.org": { - "pruning": "-", - "s": "50002", - "t": "50001", - "version": "1.4" - }, "orannis.com": { "pruning": "-", "s": "50002", From 43487910c7bd4868f3fd43054dd36a2dee5d0fc5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 3 Feb 2019 20:04:33 +0100 Subject: [PATCH 18/78] qt network dialog: use intenum for columns --- electrum/gui/qt/network_dialog.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 43a4e1de..24a8a8b3 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -24,6 +24,7 @@ # SOFTWARE. import socket +from enum import IntEnum from PyQt5.QtGui import * from PyQt5.QtCore import * @@ -132,6 +133,11 @@ class NodesListWidget(QTreeWidget): class ServerListWidget(QTreeWidget): + class Columns(IntEnum): + HOST = 0 + PORT = 1 + + SERVER_STR_ROLE = Qt.UserRole + 100 def __init__(self, parent): QTreeWidget.__init__(self) @@ -145,7 +151,7 @@ class ServerListWidget(QTreeWidget): if not item: return menu = QMenu() - server = item.data(1, Qt.UserRole) + server = item.data(self.Columns.HOST, self.SERVER_STR_ROLE) menu.addAction(_("Use as server"), lambda: self.set_server(server)) menu.exec_(self.viewport().mapToGlobal(position)) @@ -176,13 +182,13 @@ class ServerListWidget(QTreeWidget): if port: x = QTreeWidgetItem([_host, port]) server = serialize_server(_host, port, protocol) - x.setData(1, Qt.UserRole, server) + x.setData(self.Columns.HOST, self.SERVER_STR_ROLE, server) self.addTopLevelItem(x) h = self.header() h.setStretchLastSection(False) - h.setSectionResizeMode(0, QHeaderView.Stretch) - h.setSectionResizeMode(1, QHeaderView.ResizeToContents) + h.setSectionResizeMode(self.Columns.HOST, QHeaderView.Stretch) + h.setSectionResizeMode(self.Columns.PORT, QHeaderView.ResizeToContents) super().update() From ca931f476fc20e0c2003d4eb4fcafceb85e3972e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 3 Feb 2019 23:40:49 +0100 Subject: [PATCH 19/78] fix android build: pin buildozer and pin kivy. old p4a did not work with new buildozer. kivy master crashes. kivy latest release has runtime issues (orientation was landscape). these versions seem to work. also updated dockerfile to more closely match p4a master. --- electrum/gui/kivy/tools/Dockerfile | 37 +++++++++++++++----------- electrum/gui/kivy/tools/buildozer.spec | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/electrum/gui/kivy/tools/Dockerfile b/electrum/gui/kivy/tools/Dockerfile index 67661da0..44887d8e 100644 --- a/electrum/gui/kivy/tools/Dockerfile +++ b/electrum/gui/kivy/tools/Dockerfile @@ -4,10 +4,17 @@ FROM ubuntu:18.04 ENV ANDROID_HOME="/opt/android" +# configure locale +RUN apt update -qq > /dev/null && apt install -qq --yes --no-install-recommends \ + locales && \ + locale-gen en_US.UTF-8 +ENV LANG="en_US.UTF-8" \ + LANGUAGE="en_US.UTF-8" \ + LC_ALL="en_US.UTF-8" + RUN apt -y update -qq \ - && apt -y install -qq --no-install-recommends curl unzip git python3-pip python3-setuptools \ - && apt -y autoremove \ - && apt -y clean + && apt -y install -qq --no-install-recommends curl unzip ca-certificates \ + && apt -y autoremove ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" @@ -32,6 +39,7 @@ ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" # get the latest version from https://developer.android.com/studio/index.html ENV ANDROID_SDK_TOOLS_VERSION="4333796" +ENV ANDROID_SDK_BUILD_TOOLS_VERSION="28.0.3" ENV ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip" ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" @@ -51,14 +59,13 @@ RUN mkdir --parents "${ANDROID_SDK_HOME}/.android/" \ # accept Android licenses (JDK necessary!) RUN apt -y update -qq \ && apt -y install -qq --no-install-recommends openjdk-8-jdk \ - && apt -y autoremove \ - && apt -y clean -RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" --licenses > /dev/null + && apt -y autoremove +RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null # download platforms, API, build tools RUN "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-24" > /dev/null && \ "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-28" > /dev/null && \ - "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;28.0.3" > /dev/null && \ + "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null && \ "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "extras;android;m2repository" > /dev/null && \ chmod +x "${ANDROID_SDK_HOME}/tools/bin/avdmanager" @@ -71,8 +78,9 @@ ENV WORK_DIR="${HOME_DIR}/wspace" \ # install system dependencies RUN apt -y update -qq \ && apt -y install -qq --no-install-recommends \ - python virtualenv python-pip wget lbzip2 patch sudo \ - software-properties-common + python3 virtualenv python3-pip python3-setuptools git wget lbzip2 patch sudo \ + software-properties-common \ + && apt -y autoremove # install kivy RUN add-apt-repository ppa:kivy-team/kivy \ @@ -87,7 +95,7 @@ RUN python3 -m pip install image RUN dpkg --add-architecture i386 \ && apt -y update -qq \ && apt -y install -qq --no-install-recommends \ - build-essential ccache git python2.7 python2.7-dev \ + build-essential ccache git python3 python3-dev \ libncurses5:i386 libstdc++6:i386 libgtk2.0-0:i386 \ libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 \ zip zlib1g-dev zlib1g:i386 \ @@ -97,8 +105,7 @@ RUN dpkg --add-architecture i386 \ # specific recipes dependencies (e.g. libffi requires autoreconf binary) RUN apt -y update -qq \ && apt -y install -qq --no-install-recommends \ - autoconf automake cmake gettext libltdl-dev libtool pkg-config \ - python3.7 \ + libffi-dev autoconf automake cmake gettext libltdl-dev libtool pkg-config \ && apt -y autoremove \ && apt -y clean @@ -119,8 +126,7 @@ RUN chown ${USER} /opt USER ${USER} -RUN pip install --upgrade cython==0.29 -RUN python3 -m pip install --upgrade cython==0.29 +RUN python3 -m pip install --upgrade cython==0.28.6 # prepare git RUN git config --global user.name "John Doe" \ @@ -130,6 +136,7 @@ RUN git config --global user.name "John Doe" \ RUN cd /opt \ && git clone https://github.com/kivy/buildozer \ && cd buildozer \ + && git checkout 88e4a4b0c7733eec1d14c00579ec412fb59ad7f2 \ && python3 -m pip install -e . # install python-for-android @@ -138,7 +145,7 @@ RUN cd /opt \ && cd python-for-android \ && git remote add sombernight https://github.com/SomberNight/python-for-android \ && git fetch --all \ - && git checkout fad5dd2fdc9b116b7621470deac501e4a7c4cc11 \ + && git checkout dec1badc3bd134a9a1c69275339423a95d63413e \ # allowBackup="false": && git cherry-pick 86eeec7c19679a5886d5e095ce0a43f1da138f87 \ && python3 -m pip install -e . diff --git a/electrum/gui/kivy/tools/buildozer.spec b/electrum/gui/kivy/tools/buildozer.spec index 302fae19..30b2ff86 100644 --- a/electrum/gui/kivy/tools/buildozer.spec +++ b/electrum/gui/kivy/tools/buildozer.spec @@ -31,7 +31,7 @@ version.filename = %(source.dir)s/electrum/version.py #version = 1.9.8 # (list) Application requirements -requirements = python3, android, openssl, plyer, kivy==master, libffi, libsecp256k1 +requirements = python3, android, openssl, plyer, kivy==b47f669f44dbda4f463bcb7d2cada639f7fed3bc, libffi, libsecp256k1 # (str) Presplash of the application #presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png From 47b07f19b98ce52f9f4c764c97a046fedda9e557 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 29 Jan 2019 21:20:43 +0100 Subject: [PATCH 20/78] build: factor out some utilities to build_tools_util.sh --- contrib/build-wine/prepare-wine.sh | 56 ++---------------------- contrib/build_tools_util.sh | 69 ++++++++++++++++++++++++++++++ contrib/osx/base.sh | 16 +------ 3 files changed, 74 insertions(+), 67 deletions(-) create mode 100755 contrib/build_tools_util.sh diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index f9e6941f..7fb3b3cd 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -24,62 +24,12 @@ PYHOME="c:/$PYTHON_FOLDER" PYTHON="wine $PYHOME/python.exe -OO -B" -# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg -verify_signature() { - local file=$1 keyring=$2 out= - if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) && - echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then - return 0 - else - echo "$out" >&2 - exit 1 - fi -} - -verify_hash() { - local file=$1 expected_hash=$2 - actual_hash=$(sha256sum $file | awk '{print $1}') - if [ "$actual_hash" == "$expected_hash" ]; then - return 0 - else - echo "$file $actual_hash (unexpected hash)" >&2 - rm "$file" - exit 1 - fi -} - -download_if_not_exist() { - local file_name=$1 url=$2 - if [ ! -e $file_name ] ; then - wget -O $PWD/$file_name "$url" - fi -} - -# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh -retry() { - local result=0 - local count=1 - while [ $count -le 3 ]; do - [ $result -ne 0 ] && { - echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2 - } - ! { "$@"; result=$?; } - [ $result -eq 0 ] && break - count=$(($count + 1)) - sleep 1 - done - - [ $count -gt 3 ] && { - echo -e "\nThe command \"$@\" failed 3 times.\n" >&2 - } - - return $result -} - # Let's begin! -here=$(dirname $(readlink -e $0)) +here="$(dirname "$(readlink -e "$0")")" set -e +. $here/../build_tools_util.sh + wine 'wineboot' # HACK to work around https://bugs.winehq.org/show_bug.cgi?id=42474#c22 diff --git a/contrib/build_tools_util.sh b/contrib/build_tools_util.sh new file mode 100755 index 00000000..94d2cdbe --- /dev/null +++ b/contrib/build_tools_util.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +BLUE='\033[0;34m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color +function info { + printf "\r💬 ${BLUE}INFO:${NC} ${1}\n" +} +function fail { + printf "\r🗯 ${RED}ERROR:${NC} ${1}\n" + exit 1 +} +function warn { + printf "\r⚠️ ${YELLOW}WARNING:${NC} ${1}\n" +} + + +# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg +function verify_signature() { + local file=$1 keyring=$2 out= + if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) && + echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then + return 0 + else + echo "$out" >&2 + exit 1 + fi +} + +function verify_hash() { + local file=$1 expected_hash=$2 + actual_hash=$(sha256sum $file | awk '{print $1}') + if [ "$actual_hash" == "$expected_hash" ]; then + return 0 + else + echo "$file $actual_hash (unexpected hash)" >&2 + rm "$file" + exit 1 + fi +} + +function download_if_not_exist() { + local file_name=$1 url=$2 + if [ ! -e $file_name ] ; then + wget -O $file_name "$url" + fi +} + +# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh +function retry() { + local result=0 + local count=1 + while [ $count -le 3 ]; do + [ $result -ne 0 ] && { + echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2 + } + ! { "$@"; result=$?; } + [ $result -eq 0 ] && break + count=$(($count + 1)) + sleep 1 + done + + [ $count -gt 3 ] && { + echo -e "\nThe command \"$@\" failed 3 times.\n" >&2 + } + + return $result +} diff --git a/contrib/osx/base.sh b/contrib/osx/base.sh index 2c22ca9c..c2e3527c 100644 --- a/contrib/osx/base.sh +++ b/contrib/osx/base.sh @@ -1,19 +1,7 @@ #!/usr/bin/env bash -RED='\033[0;31m' -BLUE='\033[0,34m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color -function info { - printf "\r💬 ${BLUE}INFO:${NC} ${1}\n" -} -function fail { - printf "\r🗯 ${RED}ERROR:${NC} ${1}\n" - exit 1 -} -function warn { - printf "\r⚠️ ${YELLOW}WARNING:${NC} ${1}\n" -} +. $(dirname "$0")/../build_tools_util.sh + function DoCodeSignMaybe { # ARGS: infoName fileOrDirName codesignIdentity infoName="$1" From a754f9fe1019e17e16282933063bffc2623849f2 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 29 Jan 2019 21:21:01 +0100 Subject: [PATCH 21/78] initial commit for building AppImages for Linux x86_64 --- contrib/build-linux/appimage/Dockerfile | 25 +++ contrib/build-linux/appimage/README.md | 41 +++++ contrib/build-linux/appimage/apprun.sh | 11 ++ contrib/build-linux/appimage/build.sh | 197 ++++++++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 contrib/build-linux/appimage/Dockerfile create mode 100644 contrib/build-linux/appimage/README.md create mode 100755 contrib/build-linux/appimage/apprun.sh create mode 100755 contrib/build-linux/appimage/build.sh diff --git a/contrib/build-linux/appimage/Dockerfile b/contrib/build-linux/appimage/Dockerfile new file mode 100644 index 00000000..7e8a3ba2 --- /dev/null +++ b/contrib/build-linux/appimage/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:14.04@sha256:cac55e5d97fad634d954d00a5c2a56d80576a08dcc01036011f26b88263f1578 + +ENV LC_ALL=C.UTF-8 LANG=C.UTF-8 + +RUN apt-get update -q && \ + apt-get install -qy \ + git \ + wget \ + make \ + autotools-dev \ + autoconf \ + libtool \ + xz-utils \ + libssl-dev \ + zlib1g-dev \ + libffi6 \ + libffi-dev \ + libusb-1.0-0-dev \ + libudev-dev \ + gettext \ + libzbar0 \ + && \ + rm -rf /var/lib/apt/lists/* && \ + apt-get autoremove -y && \ + apt-get clean diff --git a/contrib/build-linux/appimage/README.md b/contrib/build-linux/appimage/README.md new file mode 100644 index 00000000..747c84c5 --- /dev/null +++ b/contrib/build-linux/appimage/README.md @@ -0,0 +1,41 @@ +AppImage binary for Electrum +============================ + +This assumes an Ubuntu host, but it should not be too hard to adapt to another +similar system. The docker commands should be executed in the project's root +folder. + +1. Install Docker + + ``` + $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + $ sudo apt-get update + $ sudo apt-get install -y docker-ce + ``` + +2. Build image + + ``` + $ sudo docker build --no-cache -t electrum-appimage-builder-img contrib/build-linux/appimage + ``` + +3. Build binary + + ``` + $ sudo docker run -it \ + --name electrum-appimage-builder-cont \ + -v $PWD:/opt/electrum \ + --rm \ + --workdir /opt/electrum/contrib/build-linux/appimage \ + electrum-appimage-builder-img \ + ./build.sh + ``` + +4. The generated binary is in `./dist`. + + +## FAQ + +### How can I see what is included in the AppImage? +Execute the binary as follows: `./electrum*.AppImage --appimage-extract` diff --git a/contrib/build-linux/appimage/apprun.sh b/contrib/build-linux/appimage/apprun.sh new file mode 100755 index 00000000..6000c7c9 --- /dev/null +++ b/contrib/build-linux/appimage/apprun.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +APPDIR="$(dirname "$(readlink -e "$0")")" + +export LD_LIBRARY_PATH="${APPDIR}/usr/lib/:${APPDIR}/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}" +export PATH="${APPDIR}/usr/bin:${PATH}" +export LDFLAGS="-L${APPDIR}/usr/lib/x86_64-linux-gnu -L${APPDIR}/usr/lib" + +exec "${APPDIR}/usr/bin/python3.6" -s "${APPDIR}/usr/bin/electrum" "$@" diff --git a/contrib/build-linux/appimage/build.sh b/contrib/build-linux/appimage/build.sh new file mode 100755 index 00000000..35480a6c --- /dev/null +++ b/contrib/build-linux/appimage/build.sh @@ -0,0 +1,197 @@ +#!/bin/bash + +set -e + +PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/../../.." +CONTRIB="$PROJECT_ROOT/contrib" +DISTDIR="$PROJECT_ROOT/dist" +BUILDDIR="$CONTRIB/build-linux/appimage/build/appimage" +APPDIR="$BUILDDIR/electrum.AppDir" +CACHEDIR="$CONTRIB/build-linux/appimage/.cache/appimage" + +# pinned versions +PYTHON_VERSION=3.6.8 +PKG2APPIMAGE_COMMIT="83483c2971fcaa1cb0c1253acd6c731ef8404381" +LIBSECP_VERSION="452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4" + + +VERSION=`git describe --tags --dirty --always` +APPIMAGE="$DISTDIR/electrum-$VERSION-x86_64.AppImage" + +rm -rf "$BUILDDIR" +mkdir -p "$APPDIR" "$CACHEDIR" "$DISTDIR" + + +. "$CONTRIB"/build_tools_util.sh + + +info "downloading some dependencies." +download_if_not_exist "$CACHEDIR/functions.sh" "https://raw.githubusercontent.com/AppImage/pkg2appimage/$PKG2APPIMAGE_COMMIT/functions.sh" +verify_hash "$CACHEDIR/functions.sh" "a73a21a6c1d1e15c0a9f47f017ae833873d1dc6aa74a4c840c0b901bf1dcf09c" + +download_if_not_exist "$CACHEDIR/appimagetool" "https://github.com/probonopd/AppImageKit/releases/download/11/appimagetool-x86_64.AppImage" +verify_hash "$CACHEDIR/appimagetool" "c13026b9ebaa20a17e7e0a4c818a901f0faba759801d8ceab3bb6007dde00372" + +download_if_not_exist "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz" +verify_hash "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "35446241e995773b1bed7d196f4b624dadcadc8429f26282e756b2fb8a351193" + + + +info "building python." +tar xf "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" -C "$BUILDDIR" +( + cd "$BUILDDIR/Python-$PYTHON_VERSION" + export SOURCE_DATE_EPOCH=1530212462 + ./configure \ + --cache-file="$CACHEDIR/python.config.cache" \ + --prefix="$APPDIR/usr" \ + --enable-ipv6 \ + --enable-shared \ + --with-threads \ + -q + make -s + make -s install > /dev/null +) + + +info "building libsecp256k1." +( + git clone https://github.com/bitcoin-core/secp256k1 "$CACHEDIR"/secp256k1 || (cd "$CACHEDIR"/secp256k1 && git pull) + cd "$CACHEDIR"/secp256k1 + git reset --hard "$LIBSECP_VERSION" + git clean -f -x -q + export SOURCE_DATE_EPOCH=1530212462 + ./autogen.sh + echo "LDFLAGS = -no-undefined" >> Makefile.am + ./configure \ + --prefix="$APPDIR/usr" \ + --enable-module-recovery \ + --enable-experimental \ + --enable-module-ecdh \ + --disable-jni \ + -q + make -s + make -s install > /dev/null +) + + +appdir_python() { + env \ + PYTHONNOUSERSITE=1 \ + LD_LIBRARY_PATH="$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}" \ + "$APPDIR/usr/bin/python3.6" "$@" +} + +python='appdir_python' + + +info "installing pip." +"$python" -m ensurepip + + +info "preparing electrum-locale." +( + cd "$PROJECT_ROOT" + git submodule update --init + + pushd "$CONTRIB"/deterministic-build/electrum-locale + if ! which msgfmt > /dev/null 2>&1; then + echo "Please install gettext" + exit 1 + fi + for i in ./locale/*; do + dir="$PROJECT_ROOT/electrum/$i/LC_MESSAGES" + mkdir -p $dir + msgfmt --output-file="$dir/electrum.mo" "$i/electrum.po" || true + done + popd +) + + +info "installing electrum and its dependencies." +mkdir -p "$CACHEDIR/pip_cache" +"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements.txt" +"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-binaries.txt" +"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-hw.txt" +"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" "$PROJECT_ROOT" + + +info "copying zbar" +cp "/usr/lib/libzbar.so.0" "$APPDIR/usr/lib/libzbar.so.0" + + +info "desktop integration." +cp "$PROJECT_ROOT/electrum.desktop" "$APPDIR/electrum.desktop" +cp "$PROJECT_ROOT/icons/electrum.png" "$APPDIR/electrum.png" + + +# add launcher +cp "$CONTRIB/build-linux/appimage/apprun.sh" "$APPDIR/AppRun" + +info "finalizing AppDir." +( + export PKG2AICOMMIT="$PKG2APPIMAGE_COMMIT" + . "$CACHEDIR/functions.sh" + + cd "$APPDIR" + # copy system dependencies + # note: temporarily move PyQt5 out of the way so + # we don't try to bundle its system dependencies. + mv "$APPDIR/usr/lib/python3.6/site-packages/PyQt5" "$BUILDDIR" + copy_deps; copy_deps; copy_deps + move_lib + mv "$BUILDDIR/PyQt5" "$APPDIR/usr/lib/python3.6/site-packages" + + # apply global appimage blacklist to exclude stuff + # move usr/include out of the way to preserve usr/include/python3.6m. + mv usr/include usr/include.tmp + delete_blacklisted + mv usr/include.tmp usr/include +) + + +info "stripping binaries from debug symbols." +strip_binaries() +{ + chmod u+w -R "$APPDIR" + { + printf '%s\0' "$APPDIR/usr/bin/python3.6" + find "$APPDIR" -type f -regex '.*\.so\(\.[0-9.]+\)?$' -print0 + } | xargs -0 --no-run-if-empty --verbose -n1 strip +} +strip_binaries + +remove_emptydirs() +{ + find "$APPDIR" -type d -empty -print0 | xargs -0 --no-run-if-empty rmdir -vp --ignore-fail-on-non-empty +} +remove_emptydirs + + +info "removing some unneeded stuff to decrease binary size." +rm -rf "$APPDIR"/usr/lib/python3.6/test +rm -rf "$APPDIR"/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/translations/qtwebengine_locales +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/resources/qtwebengine_* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/qml +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Web* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Designer* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Qml* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Quick* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Location* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Test* +rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Xml* + + +info "creating the AppImage." +( + cd "$BUILDDIR" + chmod +x "$CACHEDIR/appimagetool" + "$CACHEDIR/appimagetool" --appimage-extract + env VERSION="$VERSION" ./squashfs-root/AppRun --no-appstream --verbose "$APPDIR" "$APPIMAGE" +) + + +info "done." +ls -la "$DISTDIR" +sha256sum "$DISTDIR"/* From 66de511828a4a01994a643b119e535c7ed11e7be Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 29 Jan 2019 21:21:18 +0100 Subject: [PATCH 22/78] travis: build appimage for linux --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5c87a03c..3e92dd72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,6 +70,17 @@ jobs: script: ./contrib/osx/make_osx after_script: ls -lah dist && md5 dist/* after_success: true + - name: "AppImage build" + sudo: true + language: c + python: false + services: + - docker + install: + - sudo docker build --no-cache -t electrum-appimage-builder-img ./contrib/build-linux/appimage/ + script: + - sudo docker run --name electrum-appimage-builder-cont -v $PWD:/opt/electrum --rm --workdir /opt/electrum/contrib/build-linux/appimage electrum-appimage-builder-img ./build.sh + after_success: true - stage: release check install: - git fetch --all --tags From bc2a421d8776a8bc870a6e9521e004cd8e299b97 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 14:51:04 +0100 Subject: [PATCH 23/78] network: clean up broadcast_transaction Handle all exceptions in network.py, instead of in gui code. Send some exceptions to crash reporter; previously gui code would suppress them. --- electrum/network.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/electrum/network.py b/electrum/network.py index bd43a100..aaaac0fd 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -43,7 +43,7 @@ from aiohttp import ClientResponse from . import util from .util import (PrintError, print_error, log_exceptions, ignore_exceptions, - bfh, SilentTaskGroup, make_aiohttp_session) + bfh, SilentTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter) from .bitcoin import COIN from . import constants @@ -188,6 +188,13 @@ class TxBroadcastServerReturnedError(TxBroadcastError): str(self)) +class TxBroadcastUnknownError(TxBroadcastError): + def get_message_for_gui(self): + return "{}\n{}" \ + .format(_("Unknown error when broadcasting the transaction."), + _("Consider trying to connect to a different server, or updating Electrum.")) + + INSTANCE = None @@ -764,9 +771,15 @@ class Network(PrintError): try: out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout) # note: both 'out' and exception messages are untrusted input from the server - except aiorpcx.jsonrpc.RPCError as e: + except (RequestTimedOut, asyncio.CancelledError, asyncio.TimeoutError): + raise # pass-through + except aiorpcx.jsonrpc.CodeMessageError as e: self.print_error(f"broadcast_transaction error: {repr(e)}") raise TxBroadcastServerReturnedError(self.sanitize_tx_broadcast_response(e.message)) from e + except BaseException as e: # intentional BaseException for sanity! + self.print_error(f"broadcast_transaction error2: {repr(e)}") + send_exception_to_crash_reporter(e) + raise TxBroadcastUnknownError() from e if out != tx.txid(): self.print_error(f"unexpected txid for broadcast_transaction: {out} != {tx.txid()}") raise TxBroadcastHashMismatch(_("Server returned unexpected transaction ID.")) From 9013f6d59e4134b623688bd030d2e894c054e4bf Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 16:51:19 +0100 Subject: [PATCH 24/78] wizard: make 'stack' private --- electrum/base_wizard.py | 15 +++++++++------ electrum/gui/qt/installwizard.py | 2 +- electrum/plugins/trustedcoin/qt.py | 5 +++-- electrum/plugins/trustedcoin/trustedcoin.py | 9 +++++---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py index a5050d14..97ae29e2 100644 --- a/electrum/base_wizard.py +++ b/electrum/base_wizard.py @@ -64,7 +64,7 @@ class BaseWizard(object): self.plugins = plugins self.storage = storage self.wallet = None # type: Abstract_Wallet - self.stack = [] + self._stack = [] self.plugin = None self.keystores = [] self.is_kivy = config.get('gui') == 'kivy' @@ -76,7 +76,7 @@ class BaseWizard(object): def run(self, *args): action = args[0] args = args[1:] - self.stack.append((action, args)) + self._stack.append((action, args)) if not action: return if type(action) is tuple: @@ -91,15 +91,18 @@ class BaseWizard(object): raise Exception("unknown action", action) def can_go_back(self): - return len(self.stack)>1 + return len(self._stack) > 1 def go_back(self): if not self.can_go_back(): return - self.stack.pop() - action, args = self.stack.pop() + self._stack.pop() + action, args = self._stack.pop() self.run(action, *args) + def reset_stack(self): + self._stack = [] + def new(self): name = os.path.basename(self.storage.path) title = _("Create") + ' ' + name @@ -476,7 +479,7 @@ class BaseWizard(object): self.keystores.append(k) if len(self.keystores) == 1: xpub = k.get_master_public_key() - self.stack = [] + self.reset_stack() self.run('show_xpub_and_add_cosigners', xpub) elif len(self.keystores) < self.n: self.run('choose_keystore') diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py index 102721a8..51b20025 100644 --- a/electrum/gui/qt/installwizard.py +++ b/electrum/gui/qt/installwizard.py @@ -272,7 +272,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.')) - self.stack = [] + self.reset_stack() return self.run_and_get_wallet(get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py index 71db486a..34446fe2 100644 --- a/electrum/plugins/trustedcoin/qt.py +++ b/electrum/plugins/trustedcoin/qt.py @@ -36,6 +36,7 @@ from electrum.gui.qt.util import * from electrum.gui.qt.qrcodewidget import QRCodeWidget from electrum.gui.qt.amountedit import AmountEdit from electrum.gui.qt.main_window import StatusBarButton +from electrum.gui.qt.installwizard import InstallWizard from electrum.i18n import _ from electrum.plugin import hook from electrum.util import PrintError, is_valid_email @@ -195,7 +196,7 @@ class Plugin(TrustedCoinPlugin): vbox.addLayout(Buttons(CloseButton(d))) d.exec_() - def go_online_dialog(self, wizard): + def go_online_dialog(self, wizard: InstallWizard): msg = [ _("Your wallet file is: {}.").format(os.path.abspath(wizard.storage.path)), _("You need to be online in order to complete the creation of " @@ -206,7 +207,7 @@ class Plugin(TrustedCoinPlugin): _('If you are online, click on "{}" to continue.').format(_('Next')) ] msg = '\n\n'.join(msg) - wizard.stack = [] + wizard.reset_stack() wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('accept_terms_of_use')) def accept_terms_of_use(self, window): diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py index f8484d29..d4d293a8 100644 --- a/electrum/plugins/trustedcoin/trustedcoin.py +++ b/electrum/plugins/trustedcoin/trustedcoin.py @@ -48,6 +48,7 @@ from electrum.plugin import BasePlugin, hook from electrum.util import NotEnoughFunds, UserFacingException from electrum.storage import STO_EV_USER_PW from electrum.network import Network +from electrum.base_wizard import BaseWizard def get_signing_xpub(xtype): if not constants.net.TESTNET: @@ -491,9 +492,9 @@ class TrustedCoinPlugin(BasePlugin): def do_clear(self, window): window.wallet.is_billing = False - def show_disclaimer(self, wizard): + def show_disclaimer(self, wizard: BaseWizard): wizard.set_icon('trustedcoin-wizard.png') - wizard.stack = [] + wizard.reset_stack() wizard.confirm_dialog(title='Disclaimer', message='\n\n'.join(self.disclaimer_msg), run_next = lambda x: wizard.run('choose_seed')) def choose_seed(self, wizard): @@ -580,9 +581,9 @@ class TrustedCoinPlugin(BasePlugin): f = lambda x: self.restore_choice(wizard, seed, x) wizard.passphrase_dialog(run_next=f) if is_ext else f('') - def restore_choice(self, wizard, seed, passphrase): + def restore_choice(self, wizard: BaseWizard, seed, passphrase): wizard.set_icon('trustedcoin-wizard.png') - wizard.stack = [] + wizard.reset_stack() title = _('Restore 2FA wallet') msg = ' '.join([ 'You are going to restore a wallet protected with two-factor authentication.', From 8412b53ed59cf4f2f88dfe6577f3ef3c5a01502b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 17:07:49 +0100 Subject: [PATCH 25/78] wizard: copy/restore storage when stepping through the wizard When interacting with wizard, there is a single shared storage instance. If you go down the tree of dialogs, press "back" a couple times, go down another branch of dialogs, etc, there are side-effects on storage, which are never undone. fixes #5057 fixes #4496 --- electrum/base_wizard.py | 27 ++++++++++++++++++++------- electrum/storage.py | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py index 97ae29e2..8ec50303 100644 --- a/electrum/base_wizard.py +++ b/electrum/base_wizard.py @@ -27,7 +27,7 @@ import os import sys import traceback from functools import partial -from typing import List, TYPE_CHECKING, Tuple +from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any from . import bitcoin from . import keystore @@ -56,6 +56,12 @@ class ScriptTypeNotSupported(Exception): pass class GoBack(Exception): pass +class WizardStackItem(NamedTuple): + action: Any + args: Any + storage_data: dict + + class BaseWizard(object): def __init__(self, config: SimpleConfig, plugins: Plugins, storage: WalletStorage): @@ -64,7 +70,7 @@ class BaseWizard(object): self.plugins = plugins self.storage = storage self.wallet = None # type: Abstract_Wallet - self._stack = [] + self._stack = [] # type: List[WizardStackItem] self.plugin = None self.keystores = [] self.is_kivy = config.get('gui') == 'kivy' @@ -76,7 +82,8 @@ class BaseWizard(object): def run(self, *args): action = args[0] args = args[1:] - self._stack.append((action, args)) + storage_data = self.storage.get_all_data() + self._stack.append(WizardStackItem(action, args, storage_data)) if not action: return if type(action) is tuple: @@ -96,9 +103,15 @@ class BaseWizard(object): def go_back(self): if not self.can_go_back(): return + # pop 'current' frame self._stack.pop() - action, args = self._stack.pop() - self.run(action, *args) + # pop 'previous' frame + stack_item = self._stack.pop() + # try to undo side effects since we last entered 'previous' frame + # FIXME only self.storage is properly restored + self.storage.overwrite_all_data(stack_item.storage_data) + # rerun 'previous' frame + self.run(stack_item.action, *stack_item.args) def reset_stack(self): self._stack = [] @@ -154,8 +167,8 @@ class BaseWizard(object): def choose_multisig(self): def on_multisig(m, n): - self.multisig_type = "%dof%d"%(m, n) - self.storage.put('wallet_type', self.multisig_type) + multisig_type = "%dof%d" % (m, n) + self.storage.put('wallet_type', multisig_type) self.n = n self.run('choose_keystore') self.multisig_dialog(run_next=on_multisig) diff --git a/electrum/storage.py b/electrum/storage.py index 2ac3f46b..20d225f0 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -101,6 +101,20 @@ class JsonDB(PrintError): self.modified = True self.data.pop(key) + def get_all_data(self) -> dict: + with self.db_lock: + return copy.deepcopy(self.data) + + def overwrite_all_data(self, data: dict) -> None: + try: + json.dumps(data, cls=util.MyEncoder) + except: + self.print_error(f"json error: cannot save {repr(data)}") + return + with self.db_lock: + self.modified = True + self.data = copy.deepcopy(data) + @profiler def write(self): with self.db_lock: From 9e58d56e6db4266440bf80ad27289c94b55f2083 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 17:59:03 +0100 Subject: [PATCH 26/78] qt qrwindow: rm dead code --- electrum/gui/qt/qrwindow.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/electrum/gui/qt/qrwindow.py b/electrum/gui/qt/qrwindow.py index 3e519f3e..eca450eb 100644 --- a/electrum/gui/qt/qrwindow.py +++ b/electrum/gui/qt/qrwindow.py @@ -23,24 +23,13 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import platform - from PyQt5.QtCore import Qt -from PyQt5.QtGui import * -from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget +from PyQt5.QtWidgets import QHBoxLayout, QWidget from .qrcodewidget import QRCodeWidget + from electrum.i18n import _ -if platform.system() == 'Windows': - MONOSPACE_FONT = 'Lucida Console' -elif platform.system() == 'Darwin': - MONOSPACE_FONT = 'Monaco' -else: - MONOSPACE_FONT = 'monospace' - -column_index = 4 - class QR_Window(QWidget): From 68cd37282eef863befdd369e5a3957e2c1852bee Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 18:07:14 +0100 Subject: [PATCH 27/78] qt: set default "window icon" (only visible on Windows) --- electrum/gui/qt/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index bf770525..e8829fa4 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -97,6 +97,7 @@ class ElectrumGui(PrintError): self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) + self.app.setWindowIcon(read_QIcon("electrum.png")) # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) From 6926b8b2d4fdfa02ab67ad021cd6cce2764bb2fc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 18:29:08 +0100 Subject: [PATCH 28/78] qt update checker: handle --offline --- electrum/gui/qt/util.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 215cdc6d..09c2bb1a 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -936,11 +936,18 @@ class UpdateCheckThread(QThread, PrintError): return StrictVersion(version_num.strip()) def run(self): - try: - self.checked.emit(asyncio.run_coroutine_threadsafe(self.get_update_info(), self.main_window.network.asyncio_loop).result()) - except Exception: - self.print_error(traceback.format_exc()) + network = self.main_window.network + if not network: self.failed.emit() + return + try: + update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result() + except Exception as e: + #self.print_error(traceback.format_exc()) + self.print_error(f"got exception: '{repr(e)}'") + self.failed.emit() + else: + self.checked.emit(update_info) if __name__ == "__main__": From 67d080b34a244f6e13c43052a0f4ba3d7c6eb346 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 18:45:42 +0100 Subject: [PATCH 29/78] mv qt update checker to its own file --- electrum/gui/qt/main_window.py | 1 + electrum/gui/qt/update_checker.py | 141 ++++++++++++++++++++++++++++++ electrum/gui/qt/util.py | 134 +--------------------------- 3 files changed, 145 insertions(+), 131 deletions(-) create mode 100644 electrum/gui/qt/update_checker.py diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 340411e2..93926e95 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -75,6 +75,7 @@ from .fee_slider import FeeSlider from .util import * from .installwizard import WIF_HELP_TEXT from .history_list import HistoryList, HistoryModel +from .update_checker import UpdateCheck, UpdateCheckThread class StatusBarButton(QPushButton): diff --git a/electrum/gui/qt/update_checker.py b/electrum/gui/qt/update_checker.py new file mode 100644 index 00000000..4106ccf9 --- /dev/null +++ b/electrum/gui/qt/update_checker.py @@ -0,0 +1,141 @@ +# 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 distutils.version import StrictVersion + +from PyQt5.QtCore import Qt, QThread, pyqtSignal +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar, + QHBoxLayout, QPushButton) + +from electrum import version +from electrum import constants +from electrum import ecc +from electrum.i18n import _ +from electrum.util import PrintError, make_aiohttp_session + + +class UpdateCheck(QWidget, PrintError): + url = "https://electrum.org/version" + download_url = "https://electrum.org/#download" + + VERSION_ANNOUNCEMENT_SIGNING_KEYS = ( + "13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P", + ) + + def __init__(self, main_window, latest_version=None): + self.main_window = main_window + QWidget.__init__(self) + 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(self.main_window) + 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('

' + _("Update check failed") + '

') + 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('

' + _("There is a new update available") + '

') + url = "{u}".format(u=UpdateCheck.download_url) + self.detail_label.setText(_("You can download the new version from {}.").format(url)) + else: + self.heading_label.setText('

' + _("Already up to date") + '

') + self.detail_label.setText(_("You are already on the latest version of Electrum.")) + else: + self.heading_label.setText('

' + _("Checking for updates...") + '

') + self.detail_label.setText(_("Please wait while Electrum checks for available updates.")) + + +class UpdateCheckThread(QThread, PrintError): + checked = pyqtSignal(object) + failed = pyqtSignal() + + def __init__(self, main_window): + super().__init__() + self.main_window = main_window + + async def get_update_info(self): + async with make_aiohttp_session(proxy=self.main_window.network.proxy) as session: + 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): + self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'") + break + else: + raise Exception('no valid signature for version announcement') + return StrictVersion(version_num.strip()) + + def run(self): + network = self.main_window.network + if not network: + self.failed.emit() + return + try: + update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result() + except Exception as e: + #self.print_error(traceback.format_exc()) + self.print_error(f"got exception: '{repr(e)}'") + self.failed.emit() + else: + self.checked.emit(update_info) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 09c2bb1a..c46c6dfb 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -5,21 +5,17 @@ import sys import platform import queue import traceback -from distutils.version import StrictVersion + from functools import partial, lru_cache from typing import NamedTuple, Callable, Optional, TYPE_CHECKING -import base64 from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * -from electrum import version -from electrum import ecc -from electrum import constants from electrum.i18n import _, languages -from electrum.util import (FileImportFailed, FileExportFailed, make_aiohttp_session, - PrintError, resource_path) +from electrum.util import (FileImportFailed, FileExportFailed, + resource_path) from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED if TYPE_CHECKING: @@ -826,130 +822,6 @@ class FromList(QTreeWidget): self.header().setSectionResizeMode(1, sm) -class UpdateCheck(QWidget, PrintError): - url = "https://electrum.org/version" - download_url = "https://electrum.org/#download" - - VERSION_ANNOUNCEMENT_SIGNING_KEYS = ( - "13xjmVAB1EATPP8RshTE8S8sNwwSUM9p1P", - ) - - def __init__(self, main_window, latest_version=None): - self.main_window = main_window - QWidget.__init__(self) - 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(self.main_window) - 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('

' + _("Update check failed") + '

') - 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('

' + _("There is a new update available") + '

') - url = "{u}".format(u=UpdateCheck.download_url) - self.detail_label.setText(_("You can download the new version from {}.").format(url)) - else: - self.heading_label.setText('

' + _("Already up to date") + '

') - self.detail_label.setText(_("You are already on the latest version of Electrum.")) - else: - self.heading_label.setText('

' + _("Checking for updates...") + '

') - self.detail_label.setText(_("Please wait while Electrum checks for available updates.")) - - -class UpdateCheckThread(QThread, PrintError): - checked = pyqtSignal(object) - failed = pyqtSignal() - - def __init__(self, main_window): - super().__init__() - self.main_window = main_window - - async def get_update_info(self): - async with make_aiohttp_session(proxy=self.main_window.network.proxy) as session: - 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): - self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'") - break - else: - raise Exception('no valid signature for version announcement') - return StrictVersion(version_num.strip()) - - def run(self): - network = self.main_window.network - if not network: - self.failed.emit() - return - try: - update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result() - except Exception as e: - #self.print_error(traceback.format_exc()) - self.print_error(f"got exception: '{repr(e)}'") - self.failed.emit() - else: - self.checked.emit(update_info) - - if __name__ == "__main__": app = QApplication([]) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) From 5a1778b7fe80657dbd599149c370392d5f5ec5f5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 18:56:51 +0100 Subject: [PATCH 30/78] start using util.resource_path --- electrum/exchange_rate.py | 6 +++--- electrum/mnemonic.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index bfe99165..28fdd373 100644 --- a/electrum/exchange_rate.py +++ b/electrum/exchange_rate.py @@ -14,8 +14,8 @@ from typing import Sequence from .bitcoin import COIN from .i18n import _ -from .util import PrintError, ThreadJob, make_dir, log_exceptions -from .util import make_aiohttp_session +from .util import (PrintError, ThreadJob, make_dir, log_exceptions, + make_aiohttp_session, resource_path) from .network import Network from .simple_config import SimpleConfig @@ -394,7 +394,7 @@ def dictinvert(d): return inv def get_exchanges_and_currencies(): - path = os.path.join(os.path.dirname(__file__), 'currencies.json') + path = resource_path('currencies.json') try: with open(path, 'r', encoding='utf-8') as f: return json.loads(f.read()) diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py index 97889c0a..5d5ac040 100644 --- a/electrum/mnemonic.py +++ b/electrum/mnemonic.py @@ -30,7 +30,7 @@ import string import ecdsa -from .util import print_error +from .util import print_error, resource_path from .bitcoin import is_old_seed, is_new_seed from . import version @@ -88,7 +88,7 @@ def normalize_text(seed: str) -> str: return seed def load_wordlist(filename): - path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) + path = resource_path('wordlist', filename) with open(path, 'r', encoding='utf-8') as f: s = f.read().strip() s = unicodedata.normalize('NFKD', s) From 001b815c18fa5557758c9496f8523453d4423c25 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 19:19:42 +0100 Subject: [PATCH 31/78] wine build: upgrade wine, nsis, python wine-specific hack no longer needed with new wine version --- contrib/build-wine/docker/Dockerfile | 8 ++++---- contrib/build-wine/prepare-wine.sh | 10 +++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/contrib/build-wine/docker/Dockerfile b/contrib/build-wine/docker/Dockerfile index ab7d06d3..241f8d0f 100644 --- a/contrib/build-wine/docker/Dockerfile +++ b/contrib/build-wine/docker/Dockerfile @@ -31,10 +31,10 @@ RUN wget -nc https://dl.winehq.org/wine-builds/Release.key && \ apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \ apt-get update -q && \ apt-get install -qy \ - wine-stable-amd64:amd64=3.0.1~bionic \ - wine-stable-i386:i386=3.0.1~bionic \ - wine-stable:amd64=3.0.1~bionic \ - winehq-stable:amd64=3.0.1~bionic + wine-stable-amd64:amd64=4.0~bionic \ + wine-stable-i386:i386=4.0~bionic \ + wine-stable:amd64=4.0~bionic \ + winehq-stable:amd64=4.0~bionic RUN rm -rf /var/lib/apt/lists/* && \ apt-get autoremove -y && \ diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index 7fb3b3cd..9fa91e92 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -1,9 +1,9 @@ #!/bin/bash # Please update these carefully, some versions won't work under Wine -NSIS_FILENAME=nsis-3.03-setup.exe +NSIS_FILENAME=nsis-3.04-setup.exe NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download -NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743 +NSIS_SHA256=4e1db5a7400e348b1b46a4a11b6d9557fd84368e4ad3d4bc4c1be636c89638aa ZBAR_FILENAME=zbarw-20121031-setup.exe ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download @@ -13,7 +13,7 @@ LIBUSB_FILENAME=libusb-1.0.22.7z LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b -PYTHON_VERSION=3.6.7 +PYTHON_VERSION=3.6.8 ## These settings probably don't need change export WINEPREFIX=/opt/wine64 @@ -32,10 +32,6 @@ set -e wine 'wineboot' -# HACK to work around https://bugs.winehq.org/show_bug.cgi?id=42474#c22 -# needed for python 3.6+ -rm -f /opt/wine-stable/lib/wine/fakedlls/api-ms-win-core-path-l1-1-0.dll -rm -f /opt/wine-stable/lib/wine/api-ms-win-core-path-l1-1-0.dll.so cd /tmp/electrum-build From 2de7fd546689720d68c2d24e7f136b8bd6c690d5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 20:05:40 +0100 Subject: [PATCH 32/78] wine build: small clean-up in prepare-wine.sh --- contrib/build-wine/prepare-wine.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index 9fa91e92..84ba2561 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -55,11 +55,9 @@ for msifile in core dev exe lib pip tools; do wine msiexec /i "${msifile}.msi" /qb TARGETDIR=$PYHOME done -# upgrade pip -$PYTHON -m pip install pip --upgrade - - -$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt +# Install dependencies specific to binaries +# note that this also installs pinned versions of both pip and setuptools +$PYTHON -m pip install -r "$here"/../deterministic-build/requirements-binaries.txt # Install PyInstaller $PYTHON -m pip install pyinstaller==3.4 --no-use-pep517 @@ -69,9 +67,6 @@ download_if_not_exist $ZBAR_FILENAME "$ZBAR_URL" verify_hash $ZBAR_FILENAME "$ZBAR_SHA256" wine "$PWD/$ZBAR_FILENAME" /S -# Upgrade setuptools (so Electrum can be installed later) -$PYTHON -m pip install setuptools --upgrade - # Install NSIS installer download_if_not_exist $NSIS_FILENAME "$NSIS_URL" verify_hash $NSIS_FILENAME "$NSIS_SHA256" From a5ddb42bfdc515aed8fd70b995b196a411aca9b2 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 4 Feb 2019 22:34:59 +0100 Subject: [PATCH 33/78] win/mac binaries: fix qt icons follow-up #5055 --- contrib/build-wine/deterministic.spec | 4 ++-- contrib/osx/osx.spec | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 6445f513..b65654b2 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -42,8 +42,8 @@ datas = [ (home+'electrum/locale', 'electrum/locale'), (home+'electrum/plugins', 'electrum/plugins'), ('C:\\Program Files (x86)\\ZBar\\bin\\', '.'), - (home+'icons/*.png', 'icons'), - (home+'icons/*.svg', 'icons'), + (home+'icons/*.png', 'electrum/gui/icons'), + (home+'icons/*.svg', 'electrum/gui/icons'), ] datas += collect_data_files('trezorlib') datas += collect_data_files('safetlib') diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec index 87770397..b08facb2 100644 --- a/contrib/osx/osx.spec +++ b/contrib/osx/osx.spec @@ -76,8 +76,8 @@ datas = [ (electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'), (electrum + PYPKG + '/locale', PYPKG + '/locale'), (electrum + PYPKG + '/plugins', PYPKG + '/plugins'), - (electrum + 'icons/*.png', 'icons'), - (electrum + 'icons/*.svg', 'icons'), + (electrum + 'icons/*.png', 'electrum/gui/icons'), + (electrum + 'icons/*.svg', 'electrum/gui/icons'), ] datas += collect_data_files('trezorlib') datas += collect_data_files('safetlib') From d6986347e6b0dc7733884c7a4ecbc45a4d239c41 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 5 Feb 2019 00:59:09 +0100 Subject: [PATCH 34/78] qt icons: update remaining QIcon() constructors follow-up #5053 --- electrum/gui/qt/transaction_dialog.py | 2 +- electrum/plugins/hw_wallet/qt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index c12a680d..9178e5b5 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -140,7 +140,7 @@ class TxDialog(QDialog, MessageBoxMixin): b.setDefault(True) self.qr_button = b = QPushButton() - b.setIcon(QIcon(qr_icon)) + b.setIcon(read_QIcon(qr_icon)) b.clicked.connect(self.show_qr) self.copy_button = CopyButton(lambda: str(self.tx), parent.app) diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index a327b05e..2c5733ee 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -200,7 +200,7 @@ class QtPluginBase(object): return tooltip = self.device + '\n' + (keystore.label or 'unnamed') cb = partial(self.show_settings_dialog, window, keystore) - button = StatusBarButton(QIcon(self.icon_unpaired), tooltip, cb) + button = StatusBarButton(read_QIcon(self.icon_unpaired), tooltip, cb) button.icon_paired = self.icon_paired button.icon_unpaired = self.icon_unpaired window.statusBar().addPermanentWidget(button) From 8f2a730b3b39f2ba663c320cdb10025678a17c4c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 5 Feb 2019 18:27:01 +0100 Subject: [PATCH 35/78] add more details values to history --- electrum/wallet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electrum/wallet.py b/electrum/wallet.py index 0da7f55c..03ca9049 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -444,6 +444,7 @@ class Abstract_Wallet(AddressSynchronizer): 'height': tx_mined_status.height, 'confirmations': tx_mined_status.conf, 'timestamp': timestamp, + 'incoming': True if value>0 else False, 'value': Satoshis(value), 'balance': Satoshis(balance), 'date': timestamp_to_datetime(timestamp), @@ -523,6 +524,7 @@ class Abstract_Wallet(AddressSynchronizer): fiat_rate = self.price_at_timestamp(tx_hash, fx.timestamp_rate) fiat_value = fiat_value if fiat_value is not None else self.default_fiat_value(tx_hash, fx, value) fiat_fee = tx_fee / Decimal(COIN) * fiat_rate if tx_fee is not None else None + item['fiat_rate'] = Fiat(fiat_rate, fx.ccy) item['fiat_value'] = Fiat(fiat_value, fx.ccy) item['fiat_fee'] = Fiat(fiat_fee, fx.ccy) if fiat_fee else None item['fiat_default'] = fiat_default From 4ed8787433ddfffc1bc358f652e3373927cd4898 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 5 Feb 2019 20:33:50 +0100 Subject: [PATCH 36/78] remove 'util.py' from scripts --- electrum/network.py | 30 ++++++++++++++++++++ electrum/scripts/estimate_fee.py | 6 ++-- electrum/scripts/peers.py | 9 ++---- electrum/scripts/servers.py | 4 +-- electrum/scripts/txradar.py | 6 ++-- electrum/scripts/util.py | 47 -------------------------------- 6 files changed, 38 insertions(+), 64 deletions(-) delete mode 100644 electrum/scripts/util.py diff --git a/electrum/network.py b/electrum/network.py index aaaac0fd..15ba1fba 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -1134,3 +1134,33 @@ class Network(PrintError): assert network._loop_thread is not threading.currentThread() coro = asyncio.run_coroutine_threadsafe(network._send_http_on_proxy(method, url, **kwargs), network.asyncio_loop) return coro.result(5) + + + + # methods used in scripts + async def get_peers(self): + while not self.is_connected(): + await asyncio.sleep(1) + session = self.interface.session + return parse_servers(await session.send_request('server.peers.subscribe')) + + async def send_multiple_requests(self, servers: List[str], method: str, params: Sequence): + num_connecting = len(self.connecting) + for server in servers: + self._start_interface(server) + # sleep a bit + for _ in range(10): + if len(self.connecting) < num_connecting: + break + await asyncio.sleep(1) + responses = dict() + async def get_response(iface: Interface): + try: + res = await iface.session.send_request(method, params, timeout=10) + except Exception as e: + res = e + responses[iface.server] = res + async with TaskGroup() as group: + for interface in self.interfaces.values(): + await group.spawn(get_response(interface)) + return responses diff --git a/electrum/scripts/estimate_fee.py b/electrum/scripts/estimate_fee.py index bcb8c497..76bcc55b 100755 --- a/electrum/scripts/estimate_fee.py +++ b/electrum/scripts/estimate_fee.py @@ -7,8 +7,6 @@ from numbers import Number from electrum.network import filter_protocol, Network from electrum.util import create_and_start_event_loop, log_exceptions -import util - loop, stopping_fut, loop_thread = create_and_start_event_loop() network = Network() @@ -17,9 +15,9 @@ network.start() @log_exceptions async def f(): try: - peers = await util.get_peers(network) + peers = await network.get_peers() peers = filter_protocol(peers) - results = await util.send_request(network, peers, 'blockchain.estimatefee', [2]) + results = await network.send_multiple_requests(peers, 'blockchain.estimatefee', [2]) print(json.dumps(results, indent=4)) feerate_estimates = filter(lambda x: isinstance(x, Number), results.values()) print(f"median feerate: {median(feerate_estimates)}") diff --git a/electrum/scripts/peers.py b/electrum/scripts/peers.py index e8ec5b03..e55e716f 100755 --- a/electrum/scripts/peers.py +++ b/electrum/scripts/peers.py @@ -5,9 +5,6 @@ from electrum.network import filter_protocol, Network from electrum.util import create_and_start_event_loop, log_exceptions from electrum.blockchain import hash_raw_header -import util - - loop, stopping_fut, loop_thread = create_and_start_event_loop() network = Network() network.start() @@ -15,13 +12,13 @@ network.start() @log_exceptions async def f(): try: - peers = await util.get_peers(network) + peers = await network.get_peers() peers = filter_protocol(peers, 's') - results = await util.send_request(network, peers, 'blockchain.headers.subscribe', []) + results = await network.send_multiple_requests(peers, 'blockchain.headers.subscribe', []) for server, header in sorted(results.items(), key=lambda x: x[1].get('height')): height = header.get('height') blockhash = hash_raw_header(header.get('hex')) - print("%60s" % server, height, blockhash) + print(server, height, blockhash) finally: stopping_fut.set_result(1) diff --git a/electrum/scripts/servers.py b/electrum/scripts/servers.py index ace1798f..c9ef3c61 100755 --- a/electrum/scripts/servers.py +++ b/electrum/scripts/servers.py @@ -7,8 +7,6 @@ from electrum.network import filter_version, Network from electrum.util import create_and_start_event_loop, log_exceptions from electrum import constants -import util - # testnet? #constants.set_testnet() config = SimpleConfig({'testnet': False}) @@ -20,7 +18,7 @@ network.start() @log_exceptions async def f(): try: - peers = await util.get_peers(network) + peers = await network.get_peers() peers = filter_version(peers) print(json.dumps(peers, sort_keys=True, indent=4)) finally: diff --git a/electrum/scripts/txradar.py b/electrum/scripts/txradar.py index 8c150d8f..7fb8f7be 100755 --- a/electrum/scripts/txradar.py +++ b/electrum/scripts/txradar.py @@ -5,8 +5,6 @@ import asyncio from electrum.network import filter_protocol, Network from electrum.util import create_and_start_event_loop, log_exceptions -import util - try: txid = sys.argv[1] @@ -22,9 +20,9 @@ network.start() @log_exceptions async def f(): try: - peers = await util.get_peers(network) + peers = await network.get_peers() peers = filter_protocol(peers, 's') - results = await util.send_request(network, peers, 'blockchain.transaction.get', [txid]) + results = await network.send_multiple_requests(peers, 'blockchain.transaction.get', [txid]) r1, r2 = [], [] for k, v in results.items(): (r1 if not isinstance(v, Exception) else r2).append(k) diff --git a/electrum/scripts/util.py b/electrum/scripts/util.py deleted file mode 100644 index 0fe8663d..00000000 --- a/electrum/scripts/util.py +++ /dev/null @@ -1,47 +0,0 @@ -import asyncio -from typing import List, Sequence - -from aiorpcx import TaskGroup - -from electrum.network import parse_servers, Network -from electrum.interface import Interface - - -#electrum.util.set_verbosity(True) - -async def get_peers(network: Network): - while not network.is_connected(): - await asyncio.sleep(1) - print("waiting for network to get connected...") - interface = network.interface - session = interface.session - print(f"asking server {interface.server} for its peers") - peers = parse_servers(await session.send_request('server.peers.subscribe')) - print(f"got {len(peers)} servers") - return peers - - -async def send_request(network: Network, servers: List[str], method: str, params: Sequence): - print(f"contacting {len(servers)} servers") - num_connecting = len(network.connecting) - for server in servers: - network._start_interface(server) - # sleep a bit - for _ in range(10): - if len(network.connecting) < num_connecting: - break - await asyncio.sleep(1) - print(f"connected to {len(network.interfaces)} servers. sending request to all.") - responses = dict() - async def get_response(iface: Interface): - try: - res = await iface.session.send_request(method, params, timeout=10) - except Exception as e: - print(f"server {iface.server} errored or timed out: ({repr(e)})") - res = e - responses[iface.server] = res - async with TaskGroup() as group: - for interface in network.interfaces.values(): - await group.spawn(get_response(interface)) - print("%d answers" % len(responses)) - return responses From 7bb3e5336aa712e239261a53c1db9ecd128b4d6d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 6 Feb 2019 15:50:57 +0100 Subject: [PATCH 37/78] trezor: PIN could not be disabled fixes #5078 --- electrum/plugins/trezor/clientbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py index ed0d7b14..e31abaa1 100644 --- a/electrum/plugins/trezor/clientbase.py +++ b/electrum/plugins/trezor/clientbase.py @@ -147,7 +147,7 @@ class TrezorClientBase(PrintError): else: msg = _("Confirm on your {} device to set a PIN") with self.run_flow(msg): - trezorlib.device.change_pin(remove) + trezorlib.device.change_pin(self.client, remove) def clear_session(self): '''Clear the session to force pin (and passphrase if enabled) From 6ade5903dcd7631593a4d46b4988af1ac13ab857 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 7 Feb 2019 13:30:14 +0100 Subject: [PATCH 38/78] network: fix send_multiple_requests --- electrum/network.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/electrum/network.py b/electrum/network.py index 15ba1fba..434b9239 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -1161,6 +1161,8 @@ class Network(PrintError): res = e responses[iface.server] = res async with TaskGroup() as group: - for interface in self.interfaces.values(): - await group.spawn(get_response(interface)) + for server in servers: + interface = self.interfaces.get(server) + if interface: + await group.spawn(get_response(interface)) return responses From 6fb974227ba924c877e50defc36d7a3006660ef4 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 7 Feb 2019 13:56:11 +0100 Subject: [PATCH 39/78] fix #5082 --- electrum/storage.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/electrum/storage.py b/electrum/storage.py index 20d225f0..bb726ae1 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -74,6 +74,7 @@ class JsonDB(PrintError): self.db_lock = threading.RLock() self.data = {} self.path = os.path.normcase(os.path.abspath(path)) + self._file_exists = self.path and os.path.exists(self.path) self.modified = False def get(self, key, default=None): @@ -135,9 +136,10 @@ class JsonDB(PrintError): f.flush() os.fsync(f.fileno()) - mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE + mode = os.stat(self.path).st_mode if self.file_exists() else stat.S_IREAD | stat.S_IWRITE os.replace(temp_path, self.path) os.chmod(self.path, mode) + self._file_exists = True self.print_error("saved", self.path) self.modified = False @@ -145,7 +147,7 @@ class JsonDB(PrintError): return plaintext def file_exists(self): - return self.path and os.path.exists(self.path) + return self._file_exists class WalletStorage(JsonDB): From ba08b2279ddbdbe9bdd8fec349fa159e0b8cd147 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 7 Feb 2019 16:45:09 +0100 Subject: [PATCH 40/78] kivy build: test and document that make_locale is to be run first --- contrib/make_apk | 17 +++++++++++++++++ contrib/make_packages | 8 ++++---- contrib/make_tgz | 20 ++++++++++---------- electrum/gui/kivy/Readme.md | 12 +++++++++--- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/contrib/make_apk b/contrib/make_apk index 6940222c..f6d69187 100755 --- a/contrib/make_apk +++ b/contrib/make_apk @@ -1,5 +1,22 @@ #!/bin/bash +set -e + +CONTRIB="$(dirname "$(readlink -e "$0")")" +ROOT_FOLDER="$CONTRIB"/.. +PACKAGES="$ROOT_FOLDER"/packages/ +LOCALE="$ROOT_FOLDER"/electrum/locale/ + +if [ ! -d "$LOCALE" ]; then + echo "Run make_locale first!" + exit 1 +fi + +if [ ! -d "$PACKAGES" ]; then + echo "Run make_packages first!" + exit 1 +fi + pushd ./electrum/gui/kivy/ make theming diff --git a/contrib/make_packages b/contrib/make_packages index 0e4ac67b..56098d33 100755 --- a/contrib/make_packages +++ b/contrib/make_packages @@ -1,10 +1,10 @@ #!/bin/bash -contrib=$(dirname "$0") -test -n "$contrib" -a -d "$contrib" || exit +CONTRIB="$(dirname "$0")" +test -n "$CONTRIB" -a -d "$CONTRIB" || exit -rm "$contrib"/../packages/ -r +rm "$CONTRIB"/../packages/ -r #Install pure python modules in electrum directory -python3 -m pip install -r $contrib/deterministic-build/requirements.txt -t $contrib/../packages +python3 -m pip install -r "$CONTRIB"/deterministic-build/requirements.txt -t "$CONTRIB"/../packages diff --git a/contrib/make_tgz b/contrib/make_tgz index 5110cef3..a5a3373d 100755 --- a/contrib/make_tgz +++ b/contrib/make_tgz @@ -7,19 +7,19 @@ ROOT_FOLDER="$CONTRIB"/.. PACKAGES="$ROOT_FOLDER"/packages/ LOCALE="$ROOT_FOLDER"/electrum/locale/ +if [ ! -d "$LOCALE" ]; then + echo "Run make_locale first!" + exit 1 +fi + +if [ ! -d "$PACKAGES" ]; then + echo "Run make_packages first!" + exit 1 +fi + ( cd "$ROOT_FOLDER" - if [ ! -d "$LOCALE" ]; then - echo "Run make_locale first!" - exit 1 - fi - - if [ ! -d "$PACKAGES" ]; then - echo "Run make_packages first!" - exit 1 - fi - echo "'git clean -fx' would delete the following files: >>>" git clean -fx --dry-run echo "<<<" diff --git a/electrum/gui/kivy/Readme.md b/electrum/gui/kivy/Readme.md index edf6b9e2..85e54494 100644 --- a/electrum/gui/kivy/Readme.md +++ b/electrum/gui/kivy/Readme.md @@ -24,13 +24,19 @@ folder. $ sudo docker build -t electrum-android-builder-img electrum/gui/kivy/tools ``` -3. Prepare pure python dependencies +3. Build locale files ``` - $ sudo ./contrib/make_packages + $ ./contrib/make_locale ``` -4. Build binaries +4. Prepare pure python dependencies + + ``` + $ ./contrib/make_packages + ``` + +5. Build binaries ``` $ sudo docker run -it --rm \ From 9beabc03112a420ec0b1a069dd77ab291ab094f3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 7 Feb 2019 17:48:00 +0100 Subject: [PATCH 41/78] fix prev: run make_locale before building android apk --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3e92dd72..51830220 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,9 +44,13 @@ jobs: - name: "Android build" language: python python: 3.7 + env: + # reset API key to not have make_locale upload stuff here + - crowdin_api_key= services: - docker install: + - pip install requests && ./contrib/make_locale - ./contrib/make_packages - sudo docker build --no-cache -t electrum-android-builder-img electrum/gui/kivy/tools script: From 2c71b9da0c3698332aa0758fe1d0e49c3ebebf9e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 7 Feb 2019 18:57:25 +0100 Subject: [PATCH 42/78] icons: instead of symlinks, just mv "icons" dir symlinks are really inconvenient on Windows (when running from cloned source) follow-up #5055 --- MANIFEST.in | 1 - contrib/build-linux/appimage/build.sh | 2 +- contrib/build-wine/deterministic.spec | 11 +++++------ contrib/build-wine/electrum.nsi | 4 ++-- contrib/osx/osx.spec | 5 ++--- electrum/gui/icons | 1 - {icons => electrum/gui/icons}/camera_dark.png | Bin {icons => electrum/gui/icons}/camera_white.png | Bin {icons => electrum/gui/icons}/clock1.png | Bin {icons => electrum/gui/icons}/clock2.png | Bin {icons => electrum/gui/icons}/clock3.png | Bin {icons => electrum/gui/icons}/clock4.png | Bin {icons => electrum/gui/icons}/clock5.pdn | Bin {icons => electrum/gui/icons}/clock5.png | Bin {icons => electrum/gui/icons}/coldcard.png | Bin {icons => electrum/gui/icons}/coldcard_unpaired.png | Bin {icons => electrum/gui/icons}/confirmed.png | Bin {icons => electrum/gui/icons}/confirmed.svg | 0 {icons => electrum/gui/icons}/copy.png | Bin {icons => electrum/gui/icons}/digitalbitbox.png | Bin .../gui/icons}/digitalbitbox_unpaired.png | Bin electrum.icns => electrum/gui/icons/electrum.icns | Bin {icons => electrum/gui/icons}/electrum.ico | Bin {icons => electrum/gui/icons}/electrum.png | Bin .../gui/icons}/electrum_dark_icon.png | Bin {icons => electrum/gui/icons}/electrum_launcher.png | Bin .../gui/icons}/electrum_light_icon.png | Bin .../gui/icons}/electrum_presplash.png | Bin {icons => electrum/gui/icons}/electrumb.png | Bin {icons => electrum/gui/icons}/expired.png | Bin {icons => electrum/gui/icons}/eye1.png | Bin {icons => electrum/gui/icons}/file.png | Bin {icons => electrum/gui/icons}/info.png | Bin {icons => electrum/gui/icons}/keepkey.png | Bin {icons => electrum/gui/icons}/keepkey_unpaired.png | Bin {icons => electrum/gui/icons}/key.png | Bin {icons => electrum/gui/icons}/ledger.png | Bin {icons => electrum/gui/icons}/ledger_unpaired.png | Bin {icons => electrum/gui/icons}/lock.png | Bin {icons => electrum/gui/icons}/lock.svg | 0 {icons => electrum/gui/icons}/microphone.png | Bin {icons => electrum/gui/icons}/network.png | Bin {icons => electrum/gui/icons}/offline_tx.png | Bin {icons => electrum/gui/icons}/preferences.png | Bin {icons => electrum/gui/icons}/preferences.svg | 0 {icons => electrum/gui/icons}/qrcode.png | Bin {icons => electrum/gui/icons}/qrcode_white.png | Bin {icons => electrum/gui/icons}/revealer.png | Bin {icons => electrum/gui/icons}/revealer_c.png | Bin {icons => electrum/gui/icons}/safe-t.png | Bin {icons => electrum/gui/icons}/safe-t_unpaired.png | Bin {icons => electrum/gui/icons}/seal.png | Bin {icons => electrum/gui/icons}/seed.png | Bin {icons => electrum/gui/icons}/speaker.png | Bin {icons => electrum/gui/icons}/status_connected.png | Bin {icons => electrum/gui/icons}/status_connected.svg | 0 .../gui/icons}/status_connected_fork.png | Bin .../gui/icons}/status_connected_proxy.png | Bin .../gui/icons}/status_connected_proxy.svg | 0 .../gui/icons}/status_connected_proxy_fork.png | Bin .../gui/icons}/status_disconnected.png | Bin .../gui/icons}/status_disconnected.svg | 0 {icons => electrum/gui/icons}/status_lagging.png | Bin {icons => electrum/gui/icons}/status_lagging.svg | 0 .../gui/icons}/status_lagging_fork.png | Bin {icons => electrum/gui/icons}/status_waiting.png | Bin {icons => electrum/gui/icons}/status_waiting.svg | 0 {icons => electrum/gui/icons}/tab_addresses.png | Bin {icons => electrum/gui/icons}/tab_coins.png | Bin {icons => electrum/gui/icons}/tab_console.png | Bin {icons => electrum/gui/icons}/tab_contacts.png | Bin {icons => electrum/gui/icons}/tab_history.png | Bin {icons => electrum/gui/icons}/tab_receive.png | Bin {icons => electrum/gui/icons}/tab_send.png | Bin {icons => electrum/gui/icons}/tor_logo.png | Bin {icons => electrum/gui/icons}/trezor.png | Bin {icons => electrum/gui/icons}/trezor_unpaired.png | Bin .../gui/icons}/trustedcoin-status.png | Bin .../gui/icons}/trustedcoin-wizard.png | Bin {icons => electrum/gui/icons}/unconfirmed.png | Bin {icons => electrum/gui/icons}/unlock.png | Bin {icons => electrum/gui/icons}/unlock.svg | 0 {icons => electrum/gui/icons}/unpaid.png | Bin {icons => electrum/gui/icons}/update.png | Bin {icons => electrum/gui/icons}/warning.png | Bin {icons => electrum/gui/icons}/zoom.png | Bin electrum/gui/kivy/main_window.py | 2 +- electrum/gui/kivy/tools/buildozer.spec | 4 ++-- setup.py | 2 +- 89 files changed, 14 insertions(+), 18 deletions(-) delete mode 120000 electrum/gui/icons rename {icons => electrum/gui/icons}/camera_dark.png (100%) rename {icons => electrum/gui/icons}/camera_white.png (100%) rename {icons => electrum/gui/icons}/clock1.png (100%) rename {icons => electrum/gui/icons}/clock2.png (100%) rename {icons => electrum/gui/icons}/clock3.png (100%) rename {icons => electrum/gui/icons}/clock4.png (100%) rename {icons => electrum/gui/icons}/clock5.pdn (100%) rename {icons => electrum/gui/icons}/clock5.png (100%) rename {icons => electrum/gui/icons}/coldcard.png (100%) rename {icons => electrum/gui/icons}/coldcard_unpaired.png (100%) rename {icons => electrum/gui/icons}/confirmed.png (100%) rename {icons => electrum/gui/icons}/confirmed.svg (100%) rename {icons => electrum/gui/icons}/copy.png (100%) rename {icons => electrum/gui/icons}/digitalbitbox.png (100%) rename {icons => electrum/gui/icons}/digitalbitbox_unpaired.png (100%) rename electrum.icns => electrum/gui/icons/electrum.icns (100%) rename {icons => electrum/gui/icons}/electrum.ico (100%) rename {icons => electrum/gui/icons}/electrum.png (100%) rename {icons => electrum/gui/icons}/electrum_dark_icon.png (100%) rename {icons => electrum/gui/icons}/electrum_launcher.png (100%) rename {icons => electrum/gui/icons}/electrum_light_icon.png (100%) rename {icons => electrum/gui/icons}/electrum_presplash.png (100%) rename {icons => electrum/gui/icons}/electrumb.png (100%) rename {icons => electrum/gui/icons}/expired.png (100%) rename {icons => electrum/gui/icons}/eye1.png (100%) rename {icons => electrum/gui/icons}/file.png (100%) rename {icons => electrum/gui/icons}/info.png (100%) rename {icons => electrum/gui/icons}/keepkey.png (100%) rename {icons => electrum/gui/icons}/keepkey_unpaired.png (100%) rename {icons => electrum/gui/icons}/key.png (100%) rename {icons => electrum/gui/icons}/ledger.png (100%) rename {icons => electrum/gui/icons}/ledger_unpaired.png (100%) rename {icons => electrum/gui/icons}/lock.png (100%) rename {icons => electrum/gui/icons}/lock.svg (100%) rename {icons => electrum/gui/icons}/microphone.png (100%) rename {icons => electrum/gui/icons}/network.png (100%) rename {icons => electrum/gui/icons}/offline_tx.png (100%) rename {icons => electrum/gui/icons}/preferences.png (100%) rename {icons => electrum/gui/icons}/preferences.svg (100%) rename {icons => electrum/gui/icons}/qrcode.png (100%) rename {icons => electrum/gui/icons}/qrcode_white.png (100%) rename {icons => electrum/gui/icons}/revealer.png (100%) rename {icons => electrum/gui/icons}/revealer_c.png (100%) rename {icons => electrum/gui/icons}/safe-t.png (100%) rename {icons => electrum/gui/icons}/safe-t_unpaired.png (100%) rename {icons => electrum/gui/icons}/seal.png (100%) rename {icons => electrum/gui/icons}/seed.png (100%) rename {icons => electrum/gui/icons}/speaker.png (100%) rename {icons => electrum/gui/icons}/status_connected.png (100%) rename {icons => electrum/gui/icons}/status_connected.svg (100%) rename {icons => electrum/gui/icons}/status_connected_fork.png (100%) rename {icons => electrum/gui/icons}/status_connected_proxy.png (100%) rename {icons => electrum/gui/icons}/status_connected_proxy.svg (100%) rename {icons => electrum/gui/icons}/status_connected_proxy_fork.png (100%) rename {icons => electrum/gui/icons}/status_disconnected.png (100%) rename {icons => electrum/gui/icons}/status_disconnected.svg (100%) rename {icons => electrum/gui/icons}/status_lagging.png (100%) rename {icons => electrum/gui/icons}/status_lagging.svg (100%) rename {icons => electrum/gui/icons}/status_lagging_fork.png (100%) rename {icons => electrum/gui/icons}/status_waiting.png (100%) rename {icons => electrum/gui/icons}/status_waiting.svg (100%) rename {icons => electrum/gui/icons}/tab_addresses.png (100%) rename {icons => electrum/gui/icons}/tab_coins.png (100%) rename {icons => electrum/gui/icons}/tab_console.png (100%) rename {icons => electrum/gui/icons}/tab_contacts.png (100%) rename {icons => electrum/gui/icons}/tab_history.png (100%) rename {icons => electrum/gui/icons}/tab_receive.png (100%) rename {icons => electrum/gui/icons}/tab_send.png (100%) rename {icons => electrum/gui/icons}/tor_logo.png (100%) rename {icons => electrum/gui/icons}/trezor.png (100%) rename {icons => electrum/gui/icons}/trezor_unpaired.png (100%) rename {icons => electrum/gui/icons}/trustedcoin-status.png (100%) rename {icons => electrum/gui/icons}/trustedcoin-wizard.png (100%) rename {icons => electrum/gui/icons}/unconfirmed.png (100%) rename {icons => electrum/gui/icons}/unlock.png (100%) rename {icons => electrum/gui/icons}/unlock.svg (100%) rename {icons => electrum/gui/icons}/unpaid.png (100%) rename {icons => electrum/gui/icons}/update.png (100%) rename {icons => electrum/gui/icons}/warning.png (100%) rename {icons => electrum/gui/icons}/zoom.png (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 34323d29..5c7667c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include contrib/requirements/requirements.txt include contrib/requirements/requirements-hw.txt recursive-include packages *.py recursive-include packages cacert.pem -graft icons graft electrum prune electrum/tests diff --git a/contrib/build-linux/appimage/build.sh b/contrib/build-linux/appimage/build.sh index 35480a6c..3e28f6d7 100755 --- a/contrib/build-linux/appimage/build.sh +++ b/contrib/build-linux/appimage/build.sh @@ -122,7 +122,7 @@ cp "/usr/lib/libzbar.so.0" "$APPDIR/usr/lib/libzbar.so.0" info "desktop integration." cp "$PROJECT_ROOT/electrum.desktop" "$APPDIR/electrum.desktop" -cp "$PROJECT_ROOT/icons/electrum.png" "$APPDIR/electrum.png" +cp "$PROJECT_ROOT/electrum/gui/icons/electrum.png" "$APPDIR/electrum.png" # add launcher diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index b65654b2..53aafd85 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -42,8 +42,7 @@ datas = [ (home+'electrum/locale', 'electrum/locale'), (home+'electrum/plugins', 'electrum/plugins'), ('C:\\Program Files (x86)\\ZBar\\bin\\', '.'), - (home+'icons/*.png', 'electrum/gui/icons'), - (home+'icons/*.svg', 'electrum/gui/icons'), + (home+'electrum/gui/icons', 'electrum/gui/icons'), ] datas += collect_data_files('trezorlib') datas += collect_data_files('safetlib') @@ -120,7 +119,7 @@ exe_standalone = EXE( debug=False, strip=None, upx=False, - icon=home+'icons/electrum.ico', + icon=home+'electrum/gui/icons/electrum.ico', console=False) # console=True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used @@ -133,7 +132,7 @@ exe_portable = EXE( debug=False, strip=None, upx=False, - icon=home+'icons/electrum.ico', + icon=home+'electrum/gui/icons/electrum.ico', console=False) ##### @@ -147,7 +146,7 @@ exe_dependent = EXE( debug=False, strip=None, upx=False, - icon=home+'icons/electrum.ico', + icon=home+'electrum/gui/icons/electrum.ico', console=False) coll = COLLECT( @@ -158,6 +157,6 @@ coll = COLLECT( strip=None, upx=True, debug=False, - icon=home+'icons/electrum.ico', + icon=home+'electrum/gui/icons/electrum.ico', console=False, name=os.path.join('dist', 'electrum')) diff --git a/contrib/build-wine/electrum.nsi b/contrib/build-wine/electrum.nsi index ed7adcc3..1946c774 100644 --- a/contrib/build-wine/electrum.nsi +++ b/contrib/build-wine/electrum.nsi @@ -72,7 +72,7 @@ !define MUI_ABORTWARNING !define MUI_ABORTWARNING_TEXT "Are you sure you wish to abort the installation of ${PRODUCT_NAME}?" - !define MUI_ICON "c:\electrum\icons\electrum.ico" + !define MUI_ICON "c:\electrum\electrum\gui\icons\electrum.ico" ;-------------------------------- ;Pages @@ -111,7 +111,7 @@ Section ;Files to pack into the installer File /r "dist\electrum\*.*" - File "c:\electrum\icons\electrum.ico" + File "c:\electrum\electrum\gui\icons\electrum.ico" ;Store installation folder WriteRegStr HKCU "Software\${PRODUCT_NAME}" "" $INSTDIR diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec index b08facb2..2bf74f2f 100644 --- a/contrib/osx/osx.spec +++ b/contrib/osx/osx.spec @@ -7,7 +7,7 @@ import sys, os PACKAGE='Electrum' PYPKG='electrum' MAIN_SCRIPT='run_electrum' -ICONS_FILE='electrum.icns' +ICONS_FILE=PYPKG + '/gui/icons/electrum.icns' APP_SIGN = os.environ.get('APP_SIGN', '') def fail(*msg): @@ -76,8 +76,7 @@ datas = [ (electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'), (electrum + PYPKG + '/locale', PYPKG + '/locale'), (electrum + PYPKG + '/plugins', PYPKG + '/plugins'), - (electrum + 'icons/*.png', 'electrum/gui/icons'), - (electrum + 'icons/*.svg', 'electrum/gui/icons'), + (electrum + PYPKG + '/gui/icons', PYPKG + '/gui/icons'), ] datas += collect_data_files('trezorlib') datas += collect_data_files('safetlib') diff --git a/electrum/gui/icons b/electrum/gui/icons deleted file mode 120000 index 9a5906b7..00000000 --- a/electrum/gui/icons +++ /dev/null @@ -1 +0,0 @@ -../../icons/ \ No newline at end of file diff --git a/icons/camera_dark.png b/electrum/gui/icons/camera_dark.png similarity index 100% rename from icons/camera_dark.png rename to electrum/gui/icons/camera_dark.png diff --git a/icons/camera_white.png b/electrum/gui/icons/camera_white.png similarity index 100% rename from icons/camera_white.png rename to electrum/gui/icons/camera_white.png diff --git a/icons/clock1.png b/electrum/gui/icons/clock1.png similarity index 100% rename from icons/clock1.png rename to electrum/gui/icons/clock1.png diff --git a/icons/clock2.png b/electrum/gui/icons/clock2.png similarity index 100% rename from icons/clock2.png rename to electrum/gui/icons/clock2.png diff --git a/icons/clock3.png b/electrum/gui/icons/clock3.png similarity index 100% rename from icons/clock3.png rename to electrum/gui/icons/clock3.png diff --git a/icons/clock4.png b/electrum/gui/icons/clock4.png similarity index 100% rename from icons/clock4.png rename to electrum/gui/icons/clock4.png diff --git a/icons/clock5.pdn b/electrum/gui/icons/clock5.pdn similarity index 100% rename from icons/clock5.pdn rename to electrum/gui/icons/clock5.pdn diff --git a/icons/clock5.png b/electrum/gui/icons/clock5.png similarity index 100% rename from icons/clock5.png rename to electrum/gui/icons/clock5.png diff --git a/icons/coldcard.png b/electrum/gui/icons/coldcard.png similarity index 100% rename from icons/coldcard.png rename to electrum/gui/icons/coldcard.png diff --git a/icons/coldcard_unpaired.png b/electrum/gui/icons/coldcard_unpaired.png similarity index 100% rename from icons/coldcard_unpaired.png rename to electrum/gui/icons/coldcard_unpaired.png diff --git a/icons/confirmed.png b/electrum/gui/icons/confirmed.png similarity index 100% rename from icons/confirmed.png rename to electrum/gui/icons/confirmed.png diff --git a/icons/confirmed.svg b/electrum/gui/icons/confirmed.svg similarity index 100% rename from icons/confirmed.svg rename to electrum/gui/icons/confirmed.svg diff --git a/icons/copy.png b/electrum/gui/icons/copy.png similarity index 100% rename from icons/copy.png rename to electrum/gui/icons/copy.png diff --git a/icons/digitalbitbox.png b/electrum/gui/icons/digitalbitbox.png similarity index 100% rename from icons/digitalbitbox.png rename to electrum/gui/icons/digitalbitbox.png diff --git a/icons/digitalbitbox_unpaired.png b/electrum/gui/icons/digitalbitbox_unpaired.png similarity index 100% rename from icons/digitalbitbox_unpaired.png rename to electrum/gui/icons/digitalbitbox_unpaired.png diff --git a/electrum.icns b/electrum/gui/icons/electrum.icns similarity index 100% rename from electrum.icns rename to electrum/gui/icons/electrum.icns diff --git a/icons/electrum.ico b/electrum/gui/icons/electrum.ico similarity index 100% rename from icons/electrum.ico rename to electrum/gui/icons/electrum.ico diff --git a/icons/electrum.png b/electrum/gui/icons/electrum.png similarity index 100% rename from icons/electrum.png rename to electrum/gui/icons/electrum.png diff --git a/icons/electrum_dark_icon.png b/electrum/gui/icons/electrum_dark_icon.png similarity index 100% rename from icons/electrum_dark_icon.png rename to electrum/gui/icons/electrum_dark_icon.png diff --git a/icons/electrum_launcher.png b/electrum/gui/icons/electrum_launcher.png similarity index 100% rename from icons/electrum_launcher.png rename to electrum/gui/icons/electrum_launcher.png diff --git a/icons/electrum_light_icon.png b/electrum/gui/icons/electrum_light_icon.png similarity index 100% rename from icons/electrum_light_icon.png rename to electrum/gui/icons/electrum_light_icon.png diff --git a/icons/electrum_presplash.png b/electrum/gui/icons/electrum_presplash.png similarity index 100% rename from icons/electrum_presplash.png rename to electrum/gui/icons/electrum_presplash.png diff --git a/icons/electrumb.png b/electrum/gui/icons/electrumb.png similarity index 100% rename from icons/electrumb.png rename to electrum/gui/icons/electrumb.png diff --git a/icons/expired.png b/electrum/gui/icons/expired.png similarity index 100% rename from icons/expired.png rename to electrum/gui/icons/expired.png diff --git a/icons/eye1.png b/electrum/gui/icons/eye1.png similarity index 100% rename from icons/eye1.png rename to electrum/gui/icons/eye1.png diff --git a/icons/file.png b/electrum/gui/icons/file.png similarity index 100% rename from icons/file.png rename to electrum/gui/icons/file.png diff --git a/icons/info.png b/electrum/gui/icons/info.png similarity index 100% rename from icons/info.png rename to electrum/gui/icons/info.png diff --git a/icons/keepkey.png b/electrum/gui/icons/keepkey.png similarity index 100% rename from icons/keepkey.png rename to electrum/gui/icons/keepkey.png diff --git a/icons/keepkey_unpaired.png b/electrum/gui/icons/keepkey_unpaired.png similarity index 100% rename from icons/keepkey_unpaired.png rename to electrum/gui/icons/keepkey_unpaired.png diff --git a/icons/key.png b/electrum/gui/icons/key.png similarity index 100% rename from icons/key.png rename to electrum/gui/icons/key.png diff --git a/icons/ledger.png b/electrum/gui/icons/ledger.png similarity index 100% rename from icons/ledger.png rename to electrum/gui/icons/ledger.png diff --git a/icons/ledger_unpaired.png b/electrum/gui/icons/ledger_unpaired.png similarity index 100% rename from icons/ledger_unpaired.png rename to electrum/gui/icons/ledger_unpaired.png diff --git a/icons/lock.png b/electrum/gui/icons/lock.png similarity index 100% rename from icons/lock.png rename to electrum/gui/icons/lock.png diff --git a/icons/lock.svg b/electrum/gui/icons/lock.svg similarity index 100% rename from icons/lock.svg rename to electrum/gui/icons/lock.svg diff --git a/icons/microphone.png b/electrum/gui/icons/microphone.png similarity index 100% rename from icons/microphone.png rename to electrum/gui/icons/microphone.png diff --git a/icons/network.png b/electrum/gui/icons/network.png similarity index 100% rename from icons/network.png rename to electrum/gui/icons/network.png diff --git a/icons/offline_tx.png b/electrum/gui/icons/offline_tx.png similarity index 100% rename from icons/offline_tx.png rename to electrum/gui/icons/offline_tx.png diff --git a/icons/preferences.png b/electrum/gui/icons/preferences.png similarity index 100% rename from icons/preferences.png rename to electrum/gui/icons/preferences.png diff --git a/icons/preferences.svg b/electrum/gui/icons/preferences.svg similarity index 100% rename from icons/preferences.svg rename to electrum/gui/icons/preferences.svg diff --git a/icons/qrcode.png b/electrum/gui/icons/qrcode.png similarity index 100% rename from icons/qrcode.png rename to electrum/gui/icons/qrcode.png diff --git a/icons/qrcode_white.png b/electrum/gui/icons/qrcode_white.png similarity index 100% rename from icons/qrcode_white.png rename to electrum/gui/icons/qrcode_white.png diff --git a/icons/revealer.png b/electrum/gui/icons/revealer.png similarity index 100% rename from icons/revealer.png rename to electrum/gui/icons/revealer.png diff --git a/icons/revealer_c.png b/electrum/gui/icons/revealer_c.png similarity index 100% rename from icons/revealer_c.png rename to electrum/gui/icons/revealer_c.png diff --git a/icons/safe-t.png b/electrum/gui/icons/safe-t.png similarity index 100% rename from icons/safe-t.png rename to electrum/gui/icons/safe-t.png diff --git a/icons/safe-t_unpaired.png b/electrum/gui/icons/safe-t_unpaired.png similarity index 100% rename from icons/safe-t_unpaired.png rename to electrum/gui/icons/safe-t_unpaired.png diff --git a/icons/seal.png b/electrum/gui/icons/seal.png similarity index 100% rename from icons/seal.png rename to electrum/gui/icons/seal.png diff --git a/icons/seed.png b/electrum/gui/icons/seed.png similarity index 100% rename from icons/seed.png rename to electrum/gui/icons/seed.png diff --git a/icons/speaker.png b/electrum/gui/icons/speaker.png similarity index 100% rename from icons/speaker.png rename to electrum/gui/icons/speaker.png diff --git a/icons/status_connected.png b/electrum/gui/icons/status_connected.png similarity index 100% rename from icons/status_connected.png rename to electrum/gui/icons/status_connected.png diff --git a/icons/status_connected.svg b/electrum/gui/icons/status_connected.svg similarity index 100% rename from icons/status_connected.svg rename to electrum/gui/icons/status_connected.svg diff --git a/icons/status_connected_fork.png b/electrum/gui/icons/status_connected_fork.png similarity index 100% rename from icons/status_connected_fork.png rename to electrum/gui/icons/status_connected_fork.png diff --git a/icons/status_connected_proxy.png b/electrum/gui/icons/status_connected_proxy.png similarity index 100% rename from icons/status_connected_proxy.png rename to electrum/gui/icons/status_connected_proxy.png diff --git a/icons/status_connected_proxy.svg b/electrum/gui/icons/status_connected_proxy.svg similarity index 100% rename from icons/status_connected_proxy.svg rename to electrum/gui/icons/status_connected_proxy.svg diff --git a/icons/status_connected_proxy_fork.png b/electrum/gui/icons/status_connected_proxy_fork.png similarity index 100% rename from icons/status_connected_proxy_fork.png rename to electrum/gui/icons/status_connected_proxy_fork.png diff --git a/icons/status_disconnected.png b/electrum/gui/icons/status_disconnected.png similarity index 100% rename from icons/status_disconnected.png rename to electrum/gui/icons/status_disconnected.png diff --git a/icons/status_disconnected.svg b/electrum/gui/icons/status_disconnected.svg similarity index 100% rename from icons/status_disconnected.svg rename to electrum/gui/icons/status_disconnected.svg diff --git a/icons/status_lagging.png b/electrum/gui/icons/status_lagging.png similarity index 100% rename from icons/status_lagging.png rename to electrum/gui/icons/status_lagging.png diff --git a/icons/status_lagging.svg b/electrum/gui/icons/status_lagging.svg similarity index 100% rename from icons/status_lagging.svg rename to electrum/gui/icons/status_lagging.svg diff --git a/icons/status_lagging_fork.png b/electrum/gui/icons/status_lagging_fork.png similarity index 100% rename from icons/status_lagging_fork.png rename to electrum/gui/icons/status_lagging_fork.png diff --git a/icons/status_waiting.png b/electrum/gui/icons/status_waiting.png similarity index 100% rename from icons/status_waiting.png rename to electrum/gui/icons/status_waiting.png diff --git a/icons/status_waiting.svg b/electrum/gui/icons/status_waiting.svg similarity index 100% rename from icons/status_waiting.svg rename to electrum/gui/icons/status_waiting.svg diff --git a/icons/tab_addresses.png b/electrum/gui/icons/tab_addresses.png similarity index 100% rename from icons/tab_addresses.png rename to electrum/gui/icons/tab_addresses.png diff --git a/icons/tab_coins.png b/electrum/gui/icons/tab_coins.png similarity index 100% rename from icons/tab_coins.png rename to electrum/gui/icons/tab_coins.png diff --git a/icons/tab_console.png b/electrum/gui/icons/tab_console.png similarity index 100% rename from icons/tab_console.png rename to electrum/gui/icons/tab_console.png diff --git a/icons/tab_contacts.png b/electrum/gui/icons/tab_contacts.png similarity index 100% rename from icons/tab_contacts.png rename to electrum/gui/icons/tab_contacts.png diff --git a/icons/tab_history.png b/electrum/gui/icons/tab_history.png similarity index 100% rename from icons/tab_history.png rename to electrum/gui/icons/tab_history.png diff --git a/icons/tab_receive.png b/electrum/gui/icons/tab_receive.png similarity index 100% rename from icons/tab_receive.png rename to electrum/gui/icons/tab_receive.png diff --git a/icons/tab_send.png b/electrum/gui/icons/tab_send.png similarity index 100% rename from icons/tab_send.png rename to electrum/gui/icons/tab_send.png diff --git a/icons/tor_logo.png b/electrum/gui/icons/tor_logo.png similarity index 100% rename from icons/tor_logo.png rename to electrum/gui/icons/tor_logo.png diff --git a/icons/trezor.png b/electrum/gui/icons/trezor.png similarity index 100% rename from icons/trezor.png rename to electrum/gui/icons/trezor.png diff --git a/icons/trezor_unpaired.png b/electrum/gui/icons/trezor_unpaired.png similarity index 100% rename from icons/trezor_unpaired.png rename to electrum/gui/icons/trezor_unpaired.png diff --git a/icons/trustedcoin-status.png b/electrum/gui/icons/trustedcoin-status.png similarity index 100% rename from icons/trustedcoin-status.png rename to electrum/gui/icons/trustedcoin-status.png diff --git a/icons/trustedcoin-wizard.png b/electrum/gui/icons/trustedcoin-wizard.png similarity index 100% rename from icons/trustedcoin-wizard.png rename to electrum/gui/icons/trustedcoin-wizard.png diff --git a/icons/unconfirmed.png b/electrum/gui/icons/unconfirmed.png similarity index 100% rename from icons/unconfirmed.png rename to electrum/gui/icons/unconfirmed.png diff --git a/icons/unlock.png b/electrum/gui/icons/unlock.png similarity index 100% rename from icons/unlock.png rename to electrum/gui/icons/unlock.png diff --git a/icons/unlock.svg b/electrum/gui/icons/unlock.svg similarity index 100% rename from icons/unlock.svg rename to electrum/gui/icons/unlock.svg diff --git a/icons/unpaid.png b/electrum/gui/icons/unpaid.png similarity index 100% rename from icons/unpaid.png rename to electrum/gui/icons/unpaid.png diff --git a/icons/update.png b/electrum/gui/icons/update.png similarity index 100% rename from icons/update.png rename to electrum/gui/icons/update.png diff --git a/icons/warning.png b/electrum/gui/icons/warning.png similarity index 100% rename from icons/warning.png rename to electrum/gui/icons/warning.png diff --git a/icons/zoom.png b/electrum/gui/icons/zoom.png similarity index 100% rename from icons/zoom.png rename to electrum/gui/icons/zoom.png diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index 33e2a392..09b1dbbf 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -671,7 +671,7 @@ class ElectrumWindow(App): self.receive_screen = None self.requests_screen = None self.address_screen = None - self.icon = "icons/electrum.png" + self.icon = "electrum/gui/icons/electrum.png" self.tabs = self.root.ids['tabs'] def update_interfaces(self, dt): diff --git a/electrum/gui/kivy/tools/buildozer.spec b/electrum/gui/kivy/tools/buildozer.spec index 30b2ff86..29f7d932 100644 --- a/electrum/gui/kivy/tools/buildozer.spec +++ b/electrum/gui/kivy/tools/buildozer.spec @@ -35,10 +35,10 @@ requirements = python3, android, openssl, plyer, kivy==b47f669f44dbda4f463bcb7d2 # (str) Presplash of the application #presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png -presplash.filename = %(source.dir)s/icons/electrum_presplash.png +presplash.filename = %(source.dir)s/electrum/gui/icons/electrum_presplash.png # (str) Icon of the application -icon.filename = %(source.dir)s/icons/electrum_launcher.png +icon.filename = %(source.dir)s/electrum/gui/icons/electrum_launcher.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait diff --git a/setup.py b/setup.py index f39a0471..947e01ce 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']: usr_share = os.path.expanduser('~/.local/share') data_files += [ (os.path.join(usr_share, 'applications/'), ['electrum.desktop']), - (os.path.join(usr_share, icons_dirname), ['icons/electrum.png']), + (os.path.join(usr_share, icons_dirname), ['electrum/gui/icons/electrum.png']), ] extras_require = { From 89bb49e117d47187ecc2aeb4da17405d12db7015 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 7 Feb 2019 20:19:07 +0100 Subject: [PATCH 43/78] mac build: install pinned pip and setuptools earlier also add --no-use-pep517 option for pyinstaller (see 4b560250a64fb18b1bf99a5bc619033dcdc47929) --- contrib/osx/make_osx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx index 6e6460fe..982d83f5 100755 --- a/contrib/osx/make_osx +++ b/contrib/osx/make_osx @@ -48,8 +48,14 @@ pyenv global $PYTHON_VERSION || \ fail "Unable to use Python $PYTHON_VERSION" +info "install dependencies specific to binaries" +# note that this also installs pinned versions of both pip and setuptools +python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user \ + || fail "Could not install pyinstaller" + + info "Installing pyinstaller" -python3 -m pip install -I --user pyinstaller==3.4 || fail "Could not install pyinstaller" +python3 -m pip install -I --user pyinstaller==3.4 --no-use-pep517 || fail "Could not install pyinstaller" info "Using these versions for building $PACKAGE:" sw_vers @@ -98,8 +104,7 @@ DoCodeSignMaybe "CalinsQRReader.app" "${d}/build/Release/CalinsQRReader.app" "$A info "Installing requirements..." -python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \ -python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \ +python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user || \ fail "Could not install requirements" info "Installing hardware wallet requirements..." From fc72e661de7929f0c7fcbc2a6be521fa92eb4b61 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 8 Feb 2019 02:24:44 +0100 Subject: [PATCH 44/78] requirements: set min version for aiohttp --- contrib/requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt index 7249a40d..de28c782 100644 --- a/contrib/requirements/requirements.txt +++ b/contrib/requirements/requirements.txt @@ -6,6 +6,6 @@ dnspython jsonrpclib-pelix qdarkstyle<2.6 aiorpcx>=0.9,<0.11 -aiohttp +aiohttp>=3.3.0 aiohttp_socks certifi From 58c2c152663001dbe796a95869da54e95b39b482 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 8 Feb 2019 08:21:18 +0100 Subject: [PATCH 45/78] follow up 6fb974227ba924c877e50defc36d7a3006660ef4 --- electrum/storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electrum/storage.py b/electrum/storage.py index bb726ae1..67540e3e 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -137,6 +137,8 @@ class JsonDB(PrintError): os.fsync(f.fileno()) mode = os.stat(self.path).st_mode if self.file_exists() else stat.S_IREAD | stat.S_IWRITE + if not file_exists(): + assert not os.path.exists(self.path) os.replace(temp_path, self.path) os.chmod(self.path, mode) self._file_exists = True From beb9f63274723174ddd8e7df23c01ad24fc95026 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 8 Feb 2019 08:27:23 +0100 Subject: [PATCH 46/78] follow-up prev --- electrum/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/storage.py b/electrum/storage.py index 67540e3e..b32564e2 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -137,7 +137,7 @@ class JsonDB(PrintError): os.fsync(f.fileno()) mode = os.stat(self.path).st_mode if self.file_exists() else stat.S_IREAD | stat.S_IWRITE - if not file_exists(): + if not self.file_exists(): assert not os.path.exists(self.path) os.replace(temp_path, self.path) os.chmod(self.path, mode) From 8f4967f7d0715a9f279ef4b3f0f8bf96ee6844bd Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 8 Feb 2019 08:59:20 +0100 Subject: [PATCH 47/78] qt wizard: select_storage --- electrum/gui/qt/__init__.py | 6 +++--- electrum/gui/qt/installwizard.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index e8829fa4..50e914aa 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -226,10 +226,10 @@ class ElectrumGui(PrintError): else: return if not wallet: - storage = WalletStorage(path, manual_upgrades=True) - wizard = InstallWizard(self.config, self.app, self.plugins, storage) + wizard = InstallWizard(self.config, self.app, self.plugins, None) try: - wallet = wizard.run_and_get_wallet(self.daemon.get_wallet) + if wizard.select_storage(path, self.daemon.get_wallet): + wallet = wizard.run_and_get_wallet() except UserCancelled: pass except GoBack as e: diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py index 51b20025..5b30a8a9 100644 --- a/electrum/gui/qt/installwizard.py +++ b/electrum/gui/qt/installwizard.py @@ -159,7 +159,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. - def run_and_get_wallet(self, get_wallet_from_daemon): + def select_storage(self, path, get_wallet_from_daemon): vbox = QVBoxLayout() hbox = QHBoxLayout() @@ -183,6 +183,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): vbox.addLayout(hbox2) self.set_layout(vbox, title=_('Electrum wallet')) + self.storage = WalletStorage(path, manual_upgrades=True) wallet_folder = os.path.dirname(self.storage.path) def on_choose(): @@ -284,7 +285,9 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): return else: raise Exception('Unexpected encryption version') + return True + def run_and_get_wallet(self): path = self.storage.path if self.storage.requires_split(): self.hide() From 1da1f0bfea4c6fa86ced750780baae3f325c6ce9 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 8 Feb 2019 11:17:48 +0100 Subject: [PATCH 48/78] fix #4984 --- electrum/daemon.py | 7 +++++++ electrum/gui/qt/main_window.py | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index b4b021dd..89c81adc 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -262,6 +262,13 @@ class Daemon(DaemonThread): def get_wallet(self, path): return self.wallets.get(path) + def delete_wallet(self, path): + self.stop_wallet(path) + if os.path.exists(path): + os.unlink(path) + return True + return False + def stop_wallet(self, path): wallet = self.wallets.pop(path, None) if not wallet: return diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 93926e95..38298239 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -2197,10 +2197,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def _delete_wallet(self, password): wallet_path = self.wallet.storage.path basename = os.path.basename(wallet_path) - self.gui_object.daemon.stop_wallet(wallet_path) + r = self.gui_object.daemon.delete_wallet(wallet_path) self.close() - os.unlink(wallet_path) - self.show_error(_("Wallet removed: {}").format(basename)) + if r: + self.show_error(_("Wallet removed: {}").format(basename)) + else: + self.show_error(_("Wallet file not found: {}").format(basename)) @protected def show_seed_dialog(self, password): From b06b8753e6c88a6d1074ca59fbd2e8c85f234129 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 8 Feb 2019 12:59:06 +0100 Subject: [PATCH 49/78] fix #5088 --- electrum/daemon.py | 3 ++- electrum/storage.py | 6 +++--- electrum/util.py | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index 89c81adc..7c05ef2b 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -37,7 +37,7 @@ from .jsonrpc import VerifyingJSONRPCServer from .version import ELECTRUM_VERSION from .network import Network from .util import (json_decode, DaemonThread, print_error, to_string, - create_and_start_event_loop, profiler) + create_and_start_event_loop, profiler, standardize_path) from .wallet import Wallet, Abstract_Wallet from .storage import WalletStorage from .commands import known_commands, Commands @@ -235,6 +235,7 @@ class Daemon(DaemonThread): return response def load_wallet(self, path, password) -> Optional[Abstract_Wallet]: + path = standardize_path(path) # wizard will be launched if we return if path in self.wallets: wallet = self.wallets[path] diff --git a/electrum/storage.py b/electrum/storage.py index b32564e2..aa6e90cf 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -35,7 +35,7 @@ import zlib from collections import defaultdict from . import util, bitcoin, ecc -from .util import PrintError, profiler, InvalidPassword, WalletFileException, bfh +from .util import PrintError, profiler, InvalidPassword, WalletFileException, bfh, standardize_path from .plugin import run_hook, plugin_loaders from .keystore import bip44_derivation @@ -73,7 +73,7 @@ class JsonDB(PrintError): def __init__(self, path): self.db_lock = threading.RLock() self.data = {} - self.path = os.path.normcase(os.path.abspath(path)) + self.path = standardize_path(path) self._file_exists = self.path and os.path.exists(self.path) self.modified = False @@ -156,7 +156,7 @@ class WalletStorage(JsonDB): def __init__(self, path, manual_upgrades=False): JsonDB.__init__(self, path) - self.print_error("wallet path", path) + self.print_error("wallet path", self.path) self.manual_upgrades = manual_upgrades self.pubkey = None if self.file_exists(): diff --git a/electrum/util.py b/electrum/util.py index f04f0c80..aa40c65a 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -407,6 +407,10 @@ def assert_file_in_datadir_available(path, config_path): 'Should be at {}'.format(path)) +def standardize_path(path): + return os.path.normcase(os.path.realpath(os.path.abspath(path))) + + def get_new_wallet_name(wallet_folder: str) -> str: i = 1 while True: From 699562c78df036c61bd7be7e07b94610e95233f1 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 8 Feb 2019 16:17:52 +0100 Subject: [PATCH 50/78] bump libsecp256k1 version --- contrib/build-linux/appimage/build.sh | 2 +- contrib/build-wine/build-secp256k1.sh | 3 ++- contrib/osx/make_osx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contrib/build-linux/appimage/build.sh b/contrib/build-linux/appimage/build.sh index 3e28f6d7..bcb71ea0 100755 --- a/contrib/build-linux/appimage/build.sh +++ b/contrib/build-linux/appimage/build.sh @@ -12,7 +12,7 @@ CACHEDIR="$CONTRIB/build-linux/appimage/.cache/appimage" # pinned versions PYTHON_VERSION=3.6.8 PKG2APPIMAGE_COMMIT="83483c2971fcaa1cb0c1253acd6c731ef8404381" -LIBSECP_VERSION="452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4" +LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" VERSION=`git describe --tags --dirty --always` diff --git a/contrib/build-wine/build-secp256k1.sh b/contrib/build-wine/build-secp256k1.sh index 30d4a598..4d137564 100755 --- a/contrib/build-wine/build-secp256k1.sh +++ b/contrib/build-wine/build-secp256k1.sh @@ -29,7 +29,8 @@ else git pull fi -git reset --hard 452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4 +LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" +git reset --hard "$LIBSECP_VERSION" git clean -f -x -q build_dll i686-w64-mingw32 # 64-bit would be: x86_64-w64-mingw32 diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx index 982d83f5..58cdb70c 100755 --- a/contrib/osx/make_osx +++ b/contrib/osx/make_osx @@ -5,7 +5,7 @@ PYTHON_VERSION=3.6.4 BUILDDIR=/tmp/electrum-build PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum -LIBSECP_VERSION=452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4 +LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" . $(dirname "$0")/base.sh From df9087970bf945715b88cdffe7968d691d91ad84 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 8 Feb 2019 16:38:59 +0100 Subject: [PATCH 51/78] update block header checkpoints --- electrum/checkpoints.json | 40 ++++++ electrum/checkpoints_testnet.json | 224 ++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) diff --git a/electrum/checkpoints.json b/electrum/checkpoints.json index 765ba8fe..12e5ebcf 100644 --- a/electrum/checkpoints.json +++ b/electrum/checkpoints.json @@ -1066,5 +1066,45 @@ [ "0000000000000000001d9d48d93793aaa85b5f6d17c176d4ef905c7e7112b1cf", 4007526641161212986792514236082843733160766044725313536 + ], + [ + "0000000000000000001877e616b546d1ba5cf9e8b8edd9eba480a4fbb9f02bce", + 3840827764407250199942201944063224491938810378873470976 + ], + [ + "00000000000000000025eb2c783f2f29d68ab4260f4b0248450c0038debc7ba4", + 3769176185135465353474348091454476000617158630021529600 + ], + [ + "0000000000000000000c61b8a7779dcc46e88ca343b9a3fcc6763917fe3b87e2", + 3616317728887026217259424694800679959591344645351669760 + ], + [ + "00000000000000000003dba9fedba6a0b92b640167eeda0d41485a3c85ac4ac6", + 3753318892370425056811838111019504329853891761930240000 + ], + [ + "0000000000000000001ac75bed7eb6169255893f99de28f24e3e0e57b6f7db7b", + 3752507758961706405692235065937346792777982719368888320 + ], + [ + "0000000000000000000e5796e9c5cdc8a8a2de84fd17287d7dfe89074de31766", + 4052052750044136275098507698196378011637603685579620352 + ], + [ + "00000000000000000015fe695e8d2e5ed3a7de81d3818ef43a444e1ee7b3ace2", + 4774638159061819979596346127394133648234752261950013440 + ], + [ + "00000000000000000015a08d0a60237487070fe0d956d5fb5fd9d21ad6d7b2d3", + 5279534360700703025330663904443631645337169341976674304 + ], + [ + "00000000000000000008f4f64baaa9b28d4476f2a000c459df492d5664320b12", + 4798269179035823348880781507454323228379569035237392384 + ], + [ + "00000000000000000028a69d9498c46b2b073752133e3e9e585965e7dab55065", + 4581847093576588582947343450056030606262879232408420352 ] ] \ No newline at end of file diff --git a/electrum/checkpoints_testnet.json b/electrum/checkpoints_testnet.json index aaa4ea28..ae6f825c 100644 --- a/electrum/checkpoints_testnet.json +++ b/electrum/checkpoints_testnet.json @@ -2658,5 +2658,229 @@ [ "000000000000287fa294ea557835d8c98bfe94c4d8b18d5b10f1b62d68957113", 0 + ], + [ + "000000000001d842f5a0dff13820ba1e151fd8c886e28e648a0be41f3a3f1cb3", + 0 + ], + [ + "000000000000906854973b2ec51409f0b78b25b074eef3f0dbb31e1060c07c3d", + 0 + ], + [ + "00000000000009e694e22b97a4757bffef74f0ccd832398b3e815171636e3a85", + 0 + ], + [ + "0000000000000594b95678610bd47671b1142eb575d1c1d4a0073f69a71a3c65", + 0 + ], + [ + "00000000000002ac6d5c058c9932f350aeef84f6e334f4e01b40be4db537f8c2", + 0 + ], + [ + "00000000000000c9a91d8277c58eab3bfda59d3068142dd54216129e5597ccbd", + 0 + ], + [ + "0000000000000051bff2f64c9078fb346d6a2a209ba5c3ffa0048c6b7027e47f", + 0 + ], + [ + "000000000000df3c366a105ce9ed82a4917c9e19f0736493894feaba2542c7cd", + 0 + ], + [ + "0000000000007c8006959f91675b2dbf6264a1172279c826ae7f561b70e88b12", + 0 + ], + [ + "0000000000015ab3720de7669e8731c84c392aae3509d937b8d883c304e0ca86", + 0 + ], + [ + "0000000000016d7156ee43da389020fb5d30f05e11498c54f7e324561d6a6039", + 0 + ], + [ + "0000000000009c9592f83d63fe39839080ced253e1d71c52bce576f823b7722a", + 0 + ], + [ + "00000000003dee6b438ddf51b831fbedb9d2ee91644aaf5866e3a85c740b3a99", + 0 + ], + [ + "00000000000155f5594d8a3ade605d1504ee9a6f6389f1c4516e974698ebb9e4", + 0 + ], + [ + "000000000001e21adfc306bf4aa2ad90e3c2aa4a43263d1bbdc70bf9f1593416", + 0 + ], + [ + "0000000000008218e84ba7d9850a5c12b77ec5d1348e7cbdfdcb86f8fe929682", + 0 + ], + [ + "00000000000054fb41b42b30fff1738104c3edca6dab47c75e4d3565bc4b9e34", + 0 + ], + [ + "0000000000002763b825c315ba35959dcc1bd8114627949ede769ac2eece8248", + 0 + ], + [ + "00000000000007437044da0baed38a28e2991c6a527f495e91739a8d9c35acbb", + 0 + ], + [ + "000000000000032d74ad8eb0a0be6b39b8e095bd9ca8537da93aae15087aafaf", + 0 + ], + [ + "000000000000006d4025181f5b54cca6d730cc26313817c6529ba9ed62cc83b3", + 0 + ], + [ + "000000001c3ad81ffea0b74d356b6886fd3381506b7c568f96c88a78815ede09", + 0 + ], + [ + "000000000140739d224af1254712d8c4e9fb9082b381baf22c628e459157ce49", + 0 + ], + [ + "000000000306491c835f1a03c8d1e17645435296d3593dacba8ab1a7d9341d38", + 0 + ], + [ + "000000000002b383618b228eb8e4cfcf269ba647b91ac6d60ddd070295709ad1", + 0 + ], + [ + "000000000000c90fc724a76407b4405032474fc8d1649817f7ad238b96856c6a", + 0 + ], + [ + "0000000000002d5a62b323a5f213152dd84e2f415a3c6c28043c0ccaaddb3229", + 0 + ], + [ + "0000000000008c086a21457ba523b682356c760538000a480650cd667a29647a", + 0 + ], + [ + "00000000000007c586d36266aa83d8cc702aa29f31e3cc01c6eeac5a0f5f9887", + 0 + ], + [ + "0000000000013bf175e35603f24758bf8d40b1f5c266e707e3ba4de6fae43a7f", + 0 + ], + [ + "00000000000096841c486983a4333afb2525549abe57e7263723b9782e9cfef1", + 0 + ], + [ + "00000000000012dfd7c4e1f40a1dd4833da2d010a33fc65c053871884146c941", + 0 + ], + [ + "0000000000000b47eb6bc8c6562b5a30cefcf81623a37f6f61cc7497a530eb33", + 0 + ], + [ + "0000000000000021ca4558aeb796f900e581c029d751f89e1a69ae9ba9f6ebb3", + 0 + ], + [ + "00000000000000a5bf9029aebb1956200304ffee31bc09f1323ae412d81fa2b2", + 0 + ], + [ + "0000000000000046f38ada53de3346d8191f69c8f3c0ba9e1950f5bf291989c4", + 0 + ], + [ + "00000000658b5a572ea407ac49a1dccf85d67d0adfc5f613b17fa3fff1d99d51", + 0 + ], + [ + "000000005d6be9ae758c520b0061feee99cd0a231f982cc074e4d0ced1f96952", + 0 + ], + [ + "0000000001aa4671747707d329a94c398c04aaf2268e551ac5d6a7f29ffd4acd", + 0 + ], + [ + "0000000004b441b97963463faca7a933469fabfa3e7b243621159e445e5c192a", + 0 + ], + [ + "0000000002ce8842113bc875330fa77f3b984a90806a5ec0bb73321fef3c76c6", + 0 + ], + [ + "0000000000019761bf9a1c6f679b880e9fb45b3f6dc1accdbdcfce01368c9377", + 0 + ], + [ + "0000000000008a069efd1a7923557be3d9584d307b2555dc0a56d66e74e083e1", + 0 + ], + [ + "000000000001c14cec52030659ef7d45318ca574f1633ef69e9c8c9bd7e45289", + 0 + ], + [ + "0000000000009cfccb8a27f66f1d9ff40c9d47449f78d82fee2465daca582ab7", + 0 + ], + [ + "0000000000007f30cfae7fbb8ff965f70d500b98be202b1dd57ea418500c922d", + 0 + ], + [ + "0000000000002cbd2dbab4352fe4979e0d5afc47f21ef575ae0e3bb620a5478a", + 0 + ], + [ + "000000000000017a872a5c7a15b3cb6e1ecf9e009759848b85c19ca6e7bd16d2", + 0 + ], + [ + "00000000000001ade79216032b49854c966a1061fd3f8c6c56a0d38d0024629e", + 0 + ], + [ + "0000000000000090b8dfe4dde9f9f8d675642db97b3649bd147f60d1fc64cd76", + 0 + ], + [ + "0000000000000109ed5f0d6fc387ad1bc45db1e522f76adce131067fc64440ec", + 0 + ], + [ + "000000000000003105650f0b8e7b4cb466cd32ff5608f59906879aff5cad64a7", + 0 + ], + [ + "0000000000000113d4262419a8aa3a4fe928c0ea81893a2d2ffee5258b2085d8", + 0 + ], + [ + "00000000000000f15b8a196b1c3568d14b5a7856da2fef7a7f5548266582ff28", + 0 + ], + [ + "0000000000000034fb9e91c8b5f7147bd1a4f089d19a266d183df6f8497d1dff", + 0 + ], + [ + "000000000000005e51ad800c9e8ab11abb4b945f5ea86b120fa140c8af6301e0", + 0 ] ] \ No newline at end of file From d78537c8c4973d1589347b0573be177b8dbeb5a3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 8 Feb 2019 19:19:02 +0100 Subject: [PATCH 52/78] rerun freeze_packages --- .../requirements-binaries.txt | 6 +- .../deterministic-build/requirements-hw.txt | 64 +++++++++---------- contrib/deterministic-build/requirements.txt | 12 ++-- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt index 12fbcf06..0be021e9 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -48,9 +48,9 @@ PyQt5-sip==4.19.13 \ --hash=sha256:a91a308a5e0cc99de1e97afd8f09f46dd7ca20cfaa5890ef254113eebaa1adff \ --hash=sha256:b0342540da479d2713edc68fb21f307473f68da896ad5c04215dae97630e0069 \ --hash=sha256:f997e21b4e26a3397cb7b255b8d1db5b9772c8e0c94b6d870a5a0ab5c27eacaa -setuptools==40.6.3 \ - --hash=sha256:3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8 \ - --hash=sha256:e2c1ce9a832f34cf7a31ed010aabcab5008eb65ce8f2aadc04622232c14bdd0b +setuptools==40.8.0 \ + --hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \ + --hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab wheel==0.32.3 \ --hash=sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6 \ --hash=sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44 diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index fad4e540..c61f4f23 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -14,35 +14,35 @@ click==7.0 \ --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 construct==2.9.45 \ --hash=sha256:2271a0efd0798679dea825ff47e22a4c550456a5db0ba8baa82f7eae0af0118c -Cython==0.29.3 \ - --hash=sha256:1327655db47beb665961d3dc0365e20c9e8e80c234513ab2c7d06ec0dd9d63eb \ - --hash=sha256:142400f13102403f43576bb92d808a668e29deda5625388cfa39fe0bcf37b3d1 \ - --hash=sha256:1b4204715141281a631337378f0c15fe660b35e1b6888ca05f1f3f49df3b97d5 \ - --hash=sha256:23aabaaf8887e6db99df2145de6742f8c92830134735778bf2ae26338f2b406f \ - --hash=sha256:2a724c6f21fdf4e3c1e8c5c862ff87f5420fdaecf53a5a0417915e483d90217f \ - --hash=sha256:2c9c8c1c6e8bd3587e5f5db6f865a42195ff2dedcaf5cdb63fdea10c98bd6246 \ - --hash=sha256:3a1be38b774423605189d60652b3d8a324fc81d213f96569720c8093784245ab \ - --hash=sha256:46be5297a76513e4d5d6e746737d4866a762cfe457e57d7c54baa7ef8fea7e9a \ - --hash=sha256:48dc2ea4c4d3f34ddcad5bc71b1f1cf49830f868832d3e5df803c811e7395b6e \ - --hash=sha256:53f33e04d2ed078ac02841741bcd536b546e1f416608084468ab30a87638a466 \ - --hash=sha256:57b10588618ca19a4cc870f381aa8805bc5fe0c62d19d7f940232ff8a373887c \ - --hash=sha256:6001038341b52301450bb9c62e5d5da825788944572679277e137ffb3596e718 \ - --hash=sha256:70bef52e735607060f327d729be35c820d9018d260a875e4f98b20ba8c4fff96 \ - --hash=sha256:7d0f76b251699be8f1f1064dcb12d4b3b2b676ce15ff30c104e0c2091a015142 \ - --hash=sha256:9440b64c1569c26a184b7c778bb221cf9987c5c8486d32cda02302c66ea78980 \ - --hash=sha256:956cc97eac6f9d3b16e3b2d2a94c5586af3403ba97945e9d88a4a0f029899646 \ - --hash=sha256:ae430ad8cce937e07ea566d1d7899eef1fedc8ec512b4d5fa37ebf2c1f879936 \ - --hash=sha256:bdb575149881978d62167dd8427402a5872a79bd83e9d51219680670e9f80b40 \ - --hash=sha256:c0ffcddd3dbdf22aae3980931112cc8b2732315a6273988f3205cf5dacf36f45 \ - --hash=sha256:c133e2efc57426974366ac74f2ef0f1171b860301ac27f72316deacff4ccdc17 \ - --hash=sha256:c6e9521d0b77eb1da89e8264eb98c8f5cda7c49a49b8128acfd35f0ca50e56d0 \ - --hash=sha256:c7cac0220ecb733024e8acfcfb6b593a007185690f2ea470d2392b72510b7187 \ - --hash=sha256:d53483820ac28f2be2ff13eedd56c0f36a4c583727b551d3d468023556e2336a \ - --hash=sha256:d60210784186d61e0ec808d5dbee5d661c7457a57f93cb5fdc456394607ce98c \ - --hash=sha256:d687fb1cd9df28c1515666174c62e54bd894a6a6d0862f89705063cd47739f83 \ - --hash=sha256:d926764d9c768a48b0a16a91696aaa25498057e060934f968fa4c5629b942d85 \ - --hash=sha256:d94a2f4ad74732f58d1c771fc5d90a62c4fe4c98d0adfecbc76cd0d8d14bf044 \ - --hash=sha256:def76a546eeec059666f5f4117dfdf9c78e50fa1f95bdd23b04618c7adf845cd +Cython==0.29.4 \ + --hash=sha256:004eeb2fc64e9db4a3bc0d65583d69769c7242d29d9335121cbab776688dc122 \ + --hash=sha256:028ee8571884a129e0d5c4d48296f6b3ea679668c096bb65fe8b2ff7ac29d707 \ + --hash=sha256:162b8b794ca9210c7039d54b6d96cd342e0404e41e7e467baae69f0252d7e52a \ + --hash=sha256:1aba4cf581d203e8fa3b6a7b432b09416e4f93c0d1f7744834acacfe3e9db424 \ + --hash=sha256:1be8f08c87b92a880f2fd19f93293e738ca8647834ad05625635320cec9ecad4 \ + --hash=sha256:21c707a811912aeb65abe8a66e5adebc759889661c8f4cf677523cd33c609084 \ + --hash=sha256:234de250ef09ba667fc6a8f6ba07712d3fe5bb8d92d70d2b958d4c56e3172c4a \ + --hash=sha256:33dad82003df518e1242ac3b0592fc63c49d65d0d37b696cb43b7d35085e6bd5 \ + --hash=sha256:54ee6cbc1397b27670e598ae15cab36e826a01605f63bf267a5fd2642bd8a147 \ + --hash=sha256:6058c57657d2704c9fad8a56458173d2f525dce4083ca46e9b99b1b35da2b27f \ + --hash=sha256:6d3065f39ea1354eba4807e2752e97d57f26d6f68bc4a4c561264ca4300c46cb \ + --hash=sha256:7059e5acac1d7a82e75e553924d9ea59b0e79203adf903cb999287fbcc8f50f1 \ + --hash=sha256:71c31e01f20a3a7273f6f38760d29170ee89e895be540481130cb173ef6b7246 \ + --hash=sha256:89225447801e8bd0f6d8e2c0807ded83af8ad7bf4086b5ecf1f22c5a68d1b3e3 \ + --hash=sha256:9783f11fe4a4af66b0aa0da68fda833c10b95edd9099a6dbe710d03bcb96adf2 \ + --hash=sha256:9a0be0aac30d71fe490a2b0377fca6e13a5242ecc01d09c7a358f1f2fcb07a80 \ + --hash=sha256:9a2cccc26dcf2df1e0048cdf63bd714f1d5dfad457f03b9938c5cc3eef74c9ab \ + --hash=sha256:b0889310f8558eb406a4a853d63553b90c621476f1b5b80b46b1ff57eef198cf \ + --hash=sha256:c46ef7b771c88512435399e5ffbc3a70079d4945123d6fbfc6211b4cfdc4e546 \ + --hash=sha256:c71a77c1047d65e5b4e614053cbb7b567c36359b2bc1d27fba23b984ab6dddd0 \ + --hash=sha256:c9361811a1a49db11efce54fedd01a5544af8db074fce471c720bdb85ec9c7a8 \ + --hash=sha256:d021a8326a1d2cdb182b0dd7f49bb42d8a4e6ddfb3c8d388ee5be26d57d49f3b \ + --hash=sha256:d1ee3d39c73a094ae5b6e2f9263ae0dc61af1b549a0869ade8c3c30325ed9f26 \ + --hash=sha256:d49d7cf82192edc6e386262a07ceb3515028afbd9009dd8ec669d2c0a9f20128 \ + --hash=sha256:dc5fc1fa072a98f152e46465aaf3e02b3ea36a9d3b8c79bfabd47b0e3ad9226c \ + --hash=sha256:e290fed7fe73860657af564e596fff87e75cfda861c067e89212970a47826cc6 \ + --hash=sha256:fcf9a9a566ab98495db641eefee471eb03df71e394ee51fdfa9b4c0b9f6928eb \ + --hash=sha256:fe8c1d2538867bf2753988a4a2d548bcb211fcbba125aa3e9092391b16f47b56 ecdsa==0.13 \ --hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \ --hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa @@ -110,9 +110,9 @@ requests==2.21.0 \ safet==0.1.4 \ --hash=sha256:522c257910f9472e9c77c487425ed286f6721c314653e232bc41c6cedece1bb1 \ --hash=sha256:b152874acdc89ff0c8b2d680bfbf020b3e53527c2ad3404489dd61a548aa56a1 -setuptools==40.6.3 \ - --hash=sha256:3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8 \ - --hash=sha256:e2c1ce9a832f34cf7a31ed010aabcab5008eb65ce8f2aadc04622232c14bdd0b +setuptools==40.8.0 \ + --hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \ + --hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 5d725c61..38f465c1 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -24,9 +24,9 @@ aiohttp==3.5.4 \ aiohttp-socks==0.2.2 \ --hash=sha256:e473ee222b001fe33798957b9ce3352b32c187cf41684f8e2259427925914993 \ --hash=sha256:eebd8939a7c3c1e3e7e1b2552c60039b4c65ef6b8b2351efcbdd98290538e310 -aiorpcX==0.10.2 \ - --hash=sha256:23a59e625ca50cdf2866a2d8bd54256e648582a8d44d4495b34252f3dbc30e23 \ - --hash=sha256:d2bf57fc46ae37d769ab3f5e58ebee4b44acab626e597b5150a201284b9808dd +aiorpcX==0.10.4 \ + --hash=sha256:7130105d31230f069b0eea4e1893c7199cfe2d89a52a31aec718d37f4449935d \ + --hash=sha256:e6dfd584f597ee3aa6a8d4cb5755c8ffbbe42754f32728561d9e5940379d5096 async_timeout==3.0.1 \ --hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \ --hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3 @@ -112,9 +112,9 @@ QDarkStyle==2.5.4 \ qrcode==6.1 \ --hash=sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5 \ --hash=sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369 -setuptools==40.6.3 \ - --hash=sha256:3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8 \ - --hash=sha256:e2c1ce9a832f34cf7a31ed010aabcab5008eb65ce8f2aadc04622232c14bdd0b +setuptools==40.8.0 \ + --hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \ + --hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 From eb96d422f7f7c7df40bb3a028b573263a1680e9a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sat, 9 Feb 2019 12:15:46 +0100 Subject: [PATCH 53/78] import version module --- electrum/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum/interface.py b/electrum/interface.py index 6f38f2c2..7a943215 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -39,7 +39,7 @@ from .util import PrintError, ignore_exceptions, log_exceptions, bfh, SilentTask from . import util from . import x509 from . import pem -from .version import ELECTRUM_VERSION, PROTOCOL_VERSION +from . import version from . import blockchain from .blockchain import Blockchain from . import constants @@ -372,7 +372,7 @@ class Interface(PrintError): self.session = session # type: NotificationSession self.session.default_timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Generic) try: - ver = await session.send_request('server.version', [ELECTRUM_VERSION, PROTOCOL_VERSION]) + ver = await session.send_request('server.version', [version.ELECTRUM_VERSION, version.PROTOCOL_VERSION]) except aiorpcx.jsonrpc.RPCError as e: raise GracefulDisconnect(e) # probably 'unsupported protocol version' if exit_early: From cd097d6bb8e5e89b25620f9ee62764bfb7b8f583 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 10 Feb 2019 06:36:58 +0100 Subject: [PATCH 54/78] qt history list: update_tx_mined_status was not updating 'date' for tx fixes #5096 --- electrum/gui/qt/history_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index ee691a42..950e44c6 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -34,7 +34,7 @@ from decimal import Decimal from electrum.address_synchronizer import TX_HEIGHT_LOCAL from electrum.i18n import _ from electrum.util import (block_explorer_URL, profiler, print_error, TxMinedInfo, - OrderedDictWithIndex, PrintError) + OrderedDictWithIndex, PrintError, timestamp_to_datetime) from .util import * @@ -290,6 +290,7 @@ class HistoryModel(QAbstractItemModel, PrintError): 'confirmations': tx_mined_info.conf, 'timestamp': tx_mined_info.timestamp, 'txpos_in_block': tx_mined_info.txpos, + 'date': timestamp_to_datetime(tx_mined_info.timestamp), }) topLeft = self.createIndex(row, 0) bottomRight = self.createIndex(row, len(HistoryColumns) - 1) From 5aafcb2875f056627885c4dd971264259f91f9fc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 10 Feb 2019 21:00:08 +0100 Subject: [PATCH 55/78] qt MyTreeView subclasses: use IntEnum for columns --- electrum/gui/qt/address_list.py | 69 +++++++++++++++++++++------------ electrum/gui/qt/contact_list.py | 32 +++++++++------ electrum/gui/qt/invoice_list.py | 30 +++++++++----- electrum/gui/qt/request_list.py | 37 ++++++++++++------ electrum/gui/qt/utxo_list.py | 27 +++++++++---- 5 files changed, 130 insertions(+), 65 deletions(-) diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py index 904b864a..ed1c6149 100644 --- a/electrum/gui/qt/address_list.py +++ b/electrum/gui/qt/address_list.py @@ -22,7 +22,9 @@ # 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. + import webbrowser +from enum import IntEnum from electrum.i18n import _ from electrum.util import block_explorer_URL @@ -32,11 +34,21 @@ from electrum.wallet import InternalAddressCorruption from .util import * + class AddressList(MyTreeView): - filter_columns = [0, 1, 2, 3] # Type, Address, Label, Balance + + class Columns(IntEnum): + TYPE = 0 + ADDRESS = 1 + LABEL = 2 + COIN_BALANCE = 3 + FIAT_BALANCE = 4 + NUM_TXS = 5 + + filter_columns = [Columns.TYPE, Columns.ADDRESS, Columns.LABEL, Columns.COIN_BALANCE] def __init__(self, parent=None): - super().__init__(parent, self.create_menu, 2) + super().__init__(parent, self.create_menu, stretch_column=self.Columns.LABEL) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) self.show_change = 0 @@ -64,11 +76,17 @@ class AddressList(MyTreeView): config.set_key('show_toolbar_addresses', state) def refresh_headers(self): - headers = [_('Type'), _('Address'), _('Label'), _('Balance')] fx = self.parent.fx if fx and fx.get_fiat_address_config(): - headers.extend([_(fx.get_currency()+' Balance')]) - headers.extend([_('Tx')]) + ccy = fx.get_currency() + else: + ccy = _('Fiat') + headers = [_('Type'), + _('Address'), + _('Label'), + _('Balance'), + ccy + ' ' + _('Balance'), + _('Tx')] self.update_headers(headers) def toggle_change(self, state): @@ -85,7 +103,7 @@ class AddressList(MyTreeView): def update(self): self.wallet = self.parent.wallet - current_address = self.current_item_user_role(col=2) + current_address = self.current_item_user_role(col=self.Columns.LABEL) if self.show_change == 1: addr_list = self.wallet.get_receiving_addresses() elif self.show_change == 2: @@ -113,45 +131,48 @@ class AddressList(MyTreeView): if fx and fx.get_fiat_address_config(): rate = fx.exchange_rate() fiat_balance = fx.value_str(balance, rate) - labels = ['', address, label, balance_text, fiat_balance, "%d"%num] - address_item = [QStandardItem(e) for e in labels] else: - labels = ['', address, label, balance_text, "%d"%num] - address_item = [QStandardItem(e) for e in labels] + fiat_balance = '' + labels = ['', address, label, balance_text, fiat_balance, "%d"%num] + address_item = [QStandardItem(e) for e in labels] # align text and set fonts for i, item in enumerate(address_item): item.setTextAlignment(Qt.AlignVCenter) - if i not in (0, 2): + if i not in (self.Columns.TYPE, self.Columns.LABEL): item.setFont(QFont(MONOSPACE_FONT)) item.setEditable(i in self.editable_columns) - if fx and fx.get_fiat_address_config(): - address_item[4].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) + address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) # setup column 0 if self.wallet.is_change(address): - address_item[0].setText(_('change')) - address_item[0].setBackground(ColorScheme.YELLOW.as_color(True)) + address_item[self.Columns.TYPE].setText(_('change')) + address_item[self.Columns.TYPE].setBackground(ColorScheme.YELLOW.as_color(True)) else: - address_item[0].setText(_('receiving')) - address_item[0].setBackground(ColorScheme.GREEN.as_color(True)) - address_item[2].setData(address, Qt.UserRole) + address_item[self.Columns.TYPE].setText(_('receiving')) + address_item[self.Columns.TYPE].setBackground(ColorScheme.GREEN.as_color(True)) + address_item[self.Columns.LABEL].setData(address, Qt.UserRole) # setup column 1 if self.wallet.is_frozen(address): - address_item[1].setBackground(ColorScheme.BLUE.as_color(True)) + address_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True)) if self.wallet.is_beyond_limit(address): - address_item[1].setBackground(ColorScheme.RED.as_color(True)) + address_item[self.Columns.ADDRESS].setBackground(ColorScheme.RED.as_color(True)) # add item count = self.model().rowCount() self.model().insertRow(count, address_item) - address_idx = self.model().index(count, 2) + address_idx = self.model().index(count, self.Columns.LABEL) if address == current_address: set_address = QPersistentModelIndex(address_idx) self.set_current_idx(set_address) + # show/hide columns + if fx and fx.get_fiat_address_config(): + self.showColumn(self.Columns.FIAT_BALANCE) + else: + self.hideColumn(self.Columns.FIAT_BALANCE) def create_menu(self, position): from electrum.wallet import Multisig_Wallet is_multisig = isinstance(self.wallet, Multisig_Wallet) can_delete = self.wallet.can_delete_address() - selected = self.selected_in_column(1) + selected = self.selected_in_column(self.Columns.ADDRESS) if not selected: return multi_select = len(selected) > 1 @@ -165,8 +186,8 @@ class AddressList(MyTreeView): return addr = addrs[0] - addr_column_title = self.model().horizontalHeaderItem(2).text() - addr_idx = idx.sibling(idx.row(), 2) + addr_column_title = self.model().horizontalHeaderItem(self.Columns.LABEL).text() + addr_idx = idx.sibling(idx.row(), self.Columns.LABEL) column_title = self.model().horizontalHeaderItem(col).text() copy_text = self.model().itemFromIndex(idx).text() diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py index e10df1d4..31d844dc 100644 --- a/electrum/gui/qt/contact_list.py +++ b/electrum/gui/qt/contact_list.py @@ -22,12 +22,13 @@ # 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. + import webbrowser +from enum import IntEnum from PyQt5.QtGui import * from PyQt5.QtCore import * -from PyQt5.QtWidgets import ( - QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem) +from PyQt5.QtWidgets import (QAbstractItemView, QMenu) from electrum.i18n import _ from electrum.bitcoin import is_address @@ -38,10 +39,17 @@ from .util import MyTreeView, import_meta_gui, export_meta_gui class ContactList(MyTreeView): - filter_columns = [0, 1] # Key, Value + + class Columns(IntEnum): + NAME = 0 + ADDRESS = 1 + + filter_columns = [Columns.NAME, Columns.ADDRESS] def __init__(self, parent): - super().__init__(parent, self.create_menu, stretch_column=0, editable_columns=[0]) + super().__init__(parent, self.create_menu, + stretch_column=self.Columns.NAME, + editable_columns=[self.Columns.NAME]) self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) @@ -61,9 +69,9 @@ class ContactList(MyTreeView): def create_menu(self, position): menu = QMenu() idx = self.indexAt(position) - column = idx.column() or 0 + column = idx.column() or self.Columns.NAME selected_keys = [] - for s_idx in self.selected_in_column(0): + for s_idx in self.selected_in_column(self.Columns.NAME): sel_key = self.model().itemFromIndex(s_idx).data(Qt.UserRole) selected_keys.append(sel_key) if not selected_keys or not idx.isValid(): @@ -91,22 +99,22 @@ class ContactList(MyTreeView): menu.exec_(self.viewport().mapToGlobal(position)) def update(self): - current_key = self.current_item_user_role(col=0) + current_key = self.current_item_user_role(col=self.Columns.NAME) self.model().clear() self.update_headers([_('Name'), _('Address')]) set_current = None for key in sorted(self.parent.contacts.keys()): contact_type, name = self.parent.contacts[key] items = [QStandardItem(x) for x in (name, key)] - items[0].setEditable(contact_type != 'openalias') - items[1].setEditable(False) - items[0].setData(key, Qt.UserRole) + items[self.Columns.NAME].setEditable(contact_type != 'openalias') + items[self.Columns.ADDRESS].setEditable(False) + items[self.Columns.NAME].setData(key, Qt.UserRole) row_count = self.model().rowCount() self.model().insertRow(row_count, items) if key == current_key: - idx = self.model().index(row_count, 0) + idx = self.model().index(row_count, self.Columns.NAME) set_current = QPersistentModelIndex(idx) self.set_current_idx(set_current) # FIXME refresh loses sort order; so set "default" here: - self.sortByColumn(0, Qt.AscendingOrder) + self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder) run_hook('update_contacts_tab', self) diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py index b82d95c4..a8261fed 100644 --- a/electrum/gui/qt/invoice_list.py +++ b/electrum/gui/qt/invoice_list.py @@ -23,6 +23,8 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from enum import IntEnum + from electrum.i18n import _ from electrum.util import format_time @@ -30,12 +32,22 @@ from .util import * class InvoiceList(MyTreeView): - filter_columns = [0, 1, 2, 3] # Date, Requestor, Description, Amount + + class Columns(IntEnum): + DATE = 0 + REQUESTOR = 1 + DESCRIPTION = 2 + AMOUNT = 3 + STATUS = 4 + + filter_columns = [Columns.DATE, Columns.REQUESTOR, Columns.DESCRIPTION, Columns.AMOUNT] def __init__(self, parent): - super().__init__(parent, self.create_menu, stretch_column=2, editable_columns=[]) + super().__init__(parent, self.create_menu, + stretch_column=self.Columns.DESCRIPTION, + editable_columns=[]) self.setSortingEnabled(True) - self.setColumnWidth(1, 200) + self.setColumnWidth(self.Columns.REQUESTOR, 200) self.setModel(QStandardItemModel(self)) self.update() @@ -43,7 +55,7 @@ class InvoiceList(MyTreeView): inv_list = self.parent.invoices.unpaid_invoices() self.model().clear() self.update_headers([_('Expires'), _('Requestor'), _('Description'), _('Amount'), _('Status')]) - self.header().setSectionResizeMode(1, QHeaderView.Interactive) + self.header().setSectionResizeMode(self.Columns.REQUESTOR, QHeaderView.Interactive) for idx, pr in enumerate(inv_list): key = pr.get_id() status = self.parent.invoices.get_status(key) @@ -53,10 +65,10 @@ class InvoiceList(MyTreeView): labels = [date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')] items = [QStandardItem(e) for e in labels] self.set_editability(items) - items[4].setIcon(read_QIcon(pr_icons.get(status))) - items[0].setData(key, role=Qt.UserRole) - items[1].setFont(QFont(MONOSPACE_FONT)) - items[3].setFont(QFont(MONOSPACE_FONT)) + items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status))) + items[self.Columns.DATE].setData(key, role=Qt.UserRole) + items[self.Columns.REQUESTOR].setFont(QFont(MONOSPACE_FONT)) + items[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT)) self.model().insertRow(idx, items) self.selectionModel().select(self.model().index(0,0), QItemSelectionModel.SelectCurrent) if self.parent.isVisible(): @@ -73,7 +85,7 @@ class InvoiceList(MyTreeView): def create_menu(self, position): idx = self.indexAt(position) item = self.model().itemFromIndex(idx) - item_col0 = self.model().itemFromIndex(idx.sibling(idx.row(), 0)) + item_col0 = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE)) if not item or not item_col0: return key = item_col0.data(Qt.UserRole) diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py index 9a725724..a6641bc7 100644 --- a/electrum/gui/qt/request_list.py +++ b/electrum/gui/qt/request_list.py @@ -23,6 +23,8 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from enum import IntEnum + from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtWidgets import QMenu from PyQt5.QtCore import Qt @@ -35,21 +37,32 @@ from electrum.wallet import InternalAddressCorruption from .util import MyTreeView, pr_tooltips, pr_icons, read_QIcon -class RequestList(MyTreeView): - filter_columns = [0, 1, 2, 3, 4] # Date, Account, Address, Description, Amount +class RequestList(MyTreeView): + + class Columns(IntEnum): + DATE = 0 + ADDRESS = 1 + SIGNATURE = 2 + DESCRIPTION = 3 + AMOUNT = 4 + STATUS = 5 + + filter_columns = [Columns.DATE, Columns.ADDRESS, Columns.SIGNATURE, Columns.DESCRIPTION, Columns.AMOUNT] def __init__(self, parent): - super().__init__(parent, self.create_menu, 3, editable_columns=[]) + super().__init__(parent, self.create_menu, + stretch_column=self.Columns.DESCRIPTION, + editable_columns=[]) self.setModel(QStandardItemModel(self)) self.setSortingEnabled(True) - self.setColumnWidth(0, 180) + self.setColumnWidth(self.Columns.DATE, 180) self.update() self.selectionModel().currentRowChanged.connect(self.item_changed) def item_changed(self, idx): # TODO use siblingAtColumn when min Qt version is >=5.11 - addr = self.model().itemFromIndex(idx.sibling(idx.row(), 1)).text() + addr = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.ADDRESS)).text() req = self.wallet.receive_requests.get(addr) if req is None: self.update() @@ -90,7 +103,7 @@ class RequestList(MyTreeView): self.model().clear() self.update_headers([_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')]) - self.hideColumn(1) # hide address column + self.hideColumn(self.Columns.ADDRESS) for req in self.wallet.get_sorted_requests(self.config): address = req['address'] if address not in domain: @@ -108,17 +121,17 @@ class RequestList(MyTreeView): items = [QStandardItem(e) for e in labels] self.set_editability(items) if signature is not None: - items[2].setIcon(read_QIcon("seal.png")) - items[2].setToolTip('signed by '+ requestor) + items[self.Columns.SIGNATURE].setIcon(read_QIcon("seal.png")) + items[self.Columns.SIGNATURE].setToolTip(f'signed by {requestor}') if status is not PR_UNKNOWN: - items[5].setIcon(read_QIcon(pr_icons.get(status))) - items[3].setData(address, Qt.UserRole) + items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status))) + items[self.Columns.DESCRIPTION].setData(address, Qt.UserRole) self.model().insertRow(self.model().rowCount(), items) def create_menu(self, position): idx = self.indexAt(position) # TODO use siblingAtColumn when min Qt version is >=5.11 - item = self.model().itemFromIndex(idx.sibling(idx.row(), 1)) + item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.ADDRESS)) if not item: return addr = item.text() @@ -130,7 +143,7 @@ class RequestList(MyTreeView): column_title = self.model().horizontalHeaderItem(column).text() column_data = item.text() menu = QMenu(self) - if column != 2: + if column != self.Columns.SIGNATURE: menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data)) menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr))) menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr)) diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 36d037ad..a0163832 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -24,17 +24,28 @@ # SOFTWARE. from typing import Optional, List +from enum import IntEnum from electrum.i18n import _ from .util import * class UTXOList(MyTreeView): + + class Columns(IntEnum): + ADDRESS = 0 + LABEL = 1 + AMOUNT = 2 + HEIGHT = 3 + OUTPOINT = 4 + headers = [ _('Address'), _('Label'), _('Amount'), _('Height'), _('Output point')] - filter_columns = [0, 1] # Address, Label + filter_columns = [Columns.ADDRESS, Columns.LABEL] def __init__(self, parent=None): - super().__init__(parent, self.create_menu, 1, editable_columns=[]) + super().__init__(parent, self.create_menu, + stretch_column=self.Columns.LABEL, + editable_columns=[]) self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) @@ -59,18 +70,18 @@ class UTXOList(MyTreeView): labels = [address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]] utxo_item = [QStandardItem(x) for x in labels] self.set_editability(utxo_item) - utxo_item[0].setFont(QFont(MONOSPACE_FONT)) - utxo_item[2].setFont(QFont(MONOSPACE_FONT)) - utxo_item[4].setFont(QFont(MONOSPACE_FONT)) - utxo_item[0].setData(name, Qt.UserRole) + utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT)) + utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT)) + utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT)) + utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole) if self.wallet.is_frozen(address): - utxo_item[0].setBackground(ColorScheme.BLUE.as_color(True)) + utxo_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True)) self.model().insertRow(idx, utxo_item) def selected_column_0_user_roles(self) -> Optional[List[str]]: if not self.model(): return None - items = self.selected_in_column(0) + items = self.selected_in_column(self.Columns.ADDRESS) if not items: return None return [x.data(Qt.UserRole) for x in items] From c23b869d3cf99311d4a9c2692454df911b10ee93 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 10 Feb 2019 21:13:53 +0100 Subject: [PATCH 56/78] qt MyTreeView subclasses: change "headers" from list to dict --- electrum/gui/qt/address_list.py | 14 ++++++++------ electrum/gui/qt/contact_list.py | 6 +++++- electrum/gui/qt/invoice_list.py | 9 ++++++++- electrum/gui/qt/request_list.py | 10 +++++++++- electrum/gui/qt/util.py | 16 ++++++++++------ electrum/gui/qt/utxo_list.py | 8 +++++++- 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py index ed1c6149..a29e3196 100644 --- a/electrum/gui/qt/address_list.py +++ b/electrum/gui/qt/address_list.py @@ -81,12 +81,14 @@ class AddressList(MyTreeView): ccy = fx.get_currency() else: ccy = _('Fiat') - headers = [_('Type'), - _('Address'), - _('Label'), - _('Balance'), - ccy + ' ' + _('Balance'), - _('Tx')] + headers = { + self.Columns.TYPE: _('Type'), + self.Columns.ADDRESS: _('Address'), + self.Columns.LABEL: _('Label'), + self.Columns.COIN_BALANCE: _('Balance'), + self.Columns.FIAT_BALANCE: ccy + ' ' + _('Balance'), + self.Columns.NUM_TXS: _('Tx'), + } self.update_headers(headers) def toggle_change(self, state): diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py index 31d844dc..9eb8b50c 100644 --- a/electrum/gui/qt/contact_list.py +++ b/electrum/gui/qt/contact_list.py @@ -44,6 +44,10 @@ class ContactList(MyTreeView): NAME = 0 ADDRESS = 1 + headers = { + Columns.NAME: _('Name'), + Columns.ADDRESS: _('Address'), + } filter_columns = [Columns.NAME, Columns.ADDRESS] def __init__(self, parent): @@ -101,7 +105,7 @@ class ContactList(MyTreeView): def update(self): current_key = self.current_item_user_role(col=self.Columns.NAME) self.model().clear() - self.update_headers([_('Name'), _('Address')]) + self.update_headers(self.__class__.headers) set_current = None for key in sorted(self.parent.contacts.keys()): contact_type, name = self.parent.contacts[key] diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py index a8261fed..f011d6bb 100644 --- a/electrum/gui/qt/invoice_list.py +++ b/electrum/gui/qt/invoice_list.py @@ -40,6 +40,13 @@ class InvoiceList(MyTreeView): AMOUNT = 3 STATUS = 4 + headers = { + Columns.DATE: _('Expires'), + Columns.REQUESTOR: _('Requestor'), + Columns.DESCRIPTION: _('Description'), + Columns.AMOUNT: _('Amount'), + Columns.STATUS: _('Status'), + } filter_columns = [Columns.DATE, Columns.REQUESTOR, Columns.DESCRIPTION, Columns.AMOUNT] def __init__(self, parent): @@ -54,7 +61,7 @@ class InvoiceList(MyTreeView): def update(self): inv_list = self.parent.invoices.unpaid_invoices() self.model().clear() - self.update_headers([_('Expires'), _('Requestor'), _('Description'), _('Amount'), _('Status')]) + self.update_headers(self.__class__.headers) self.header().setSectionResizeMode(self.Columns.REQUESTOR, QHeaderView.Interactive) for idx, pr in enumerate(inv_list): key = pr.get_id() diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py index a6641bc7..5edffa48 100644 --- a/electrum/gui/qt/request_list.py +++ b/electrum/gui/qt/request_list.py @@ -48,6 +48,14 @@ class RequestList(MyTreeView): AMOUNT = 4 STATUS = 5 + headers = { + Columns.DATE: _('Date'), + Columns.ADDRESS: _('Address'), + Columns.SIGNATURE: '', + Columns.DESCRIPTION: _('Description'), + Columns.AMOUNT: _('Amount'), + Columns.STATUS: _('Status'), + } filter_columns = [Columns.DATE, Columns.ADDRESS, Columns.SIGNATURE, Columns.DESCRIPTION, Columns.AMOUNT] def __init__(self, parent): @@ -102,7 +110,7 @@ class RequestList(MyTreeView): self.parent.new_request_button.setEnabled(addr != current_address) self.model().clear() - self.update_headers([_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')]) + self.update_headers(self.__class__.headers) self.hideColumn(self.Columns.ADDRESS) for req in self.wallet.get_sorted_requests(self.config): address = req['address'] diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index c46c6dfb..d8c2c153 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -7,7 +7,7 @@ import queue import traceback from functools import partial, lru_cache -from typing import NamedTuple, Callable, Optional, TYPE_CHECKING +from typing import NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict from PyQt5.QtGui import * from PyQt5.QtCore import * @@ -466,13 +466,17 @@ class MyTreeView(QTreeView): assert set_current.isValid() self.selectionModel().select(QModelIndex(set_current), QItemSelectionModel.SelectCurrent) - def update_headers(self, headers): + def update_headers(self, headers: Union[List[str], Dict[int, str]]): + # headers is either a list of column names, or a dict: (col_idx->col_name) + if not isinstance(headers, dict): # convert to dict + headers = dict(enumerate(headers)) + col_names = [headers[col_idx] for col_idx in sorted(headers.keys())] model = self.model() - model.setHorizontalHeaderLabels(headers) + model.setHorizontalHeaderLabels(col_names) self.header().setStretchLastSection(False) - for col in range(len(headers)): - sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents - self.header().setSectionResizeMode(col, sm) + for col_idx in headers: + sm = QHeaderView.Stretch if col_idx == self.stretch_column else QHeaderView.ResizeToContents + self.header().setSectionResizeMode(col_idx, sm) def keyPressEvent(self, event): if self.itemDelegate().opened: diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index a0163832..1055ca2d 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -39,7 +39,13 @@ class UTXOList(MyTreeView): HEIGHT = 3 OUTPOINT = 4 - headers = [ _('Address'), _('Label'), _('Amount'), _('Height'), _('Output point')] + headers = { + Columns.ADDRESS: _('Address'), + Columns.LABEL: _('Label'), + Columns.AMOUNT: _('Amount'), + Columns.HEIGHT: _('Height'), + Columns.OUTPOINT: _('Output point'), + } filter_columns = [Columns.ADDRESS, Columns.LABEL] def __init__(self, parent=None): From ebeed4736fc3dd60255f2808cbdead237caacc58 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 10 Feb 2019 21:20:44 +0100 Subject: [PATCH 57/78] qt utxo_list: show full prevout_n in outpoint column previously, if prevout_n was >=10, the ":" char or even digits were cut --- electrum/gui/qt/utxo_list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 1055ca2d..529e6d12 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -70,16 +70,18 @@ class UTXOList(MyTreeView): address = x.get('address') height = x.get('height') name = x.get('prevout_hash') + ":%d"%x.get('prevout_n') + name_short = x.get('prevout_hash')[:10] + '...' + ":%d"%x.get('prevout_n') self.utxo_dict[name] = x label = self.wallet.get_label(x.get('prevout_hash')) amount = self.parent.format_amount(x['value'], whitespaces=True) - labels = [address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]] + labels = [address, label, amount, '%d'%height, name_short] utxo_item = [QStandardItem(x) for x in labels] self.set_editability(utxo_item) utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole) + utxo_item[self.Columns.OUTPOINT].setToolTip(name) if self.wallet.is_frozen(address): utxo_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True)) self.model().insertRow(idx, utxo_item) From 8072ad1ad98485777a81e4eee31e7ff81ae0019e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 11 Feb 2019 16:36:01 +0100 Subject: [PATCH 58/78] network broadcast_transaction: make error text clearer --- electrum/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/network.py b/electrum/network.py index 434b9239..5537c5c6 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -795,7 +795,7 @@ class Network(PrintError): # grep "reason =" policy_error_messages = { r"version": _("Transaction uses non-standard version."), - r"tx-size": _("The transaction was rejected because it is too large."), + r"tx-size": _("The transaction was rejected because it is too large (in bytes)."), r"scriptsig-size": None, r"scriptsig-not-pushonly": None, r"scriptpubkey": None, From 026448837fb23de74fc40067c82b7caa8211cccf Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 11 Feb 2019 20:21:24 +0100 Subject: [PATCH 59/78] no more "import *" fixes #5101 fixes #5105 --- electrum/gui/qt/__init__.py | 11 ++++++----- electrum/gui/qt/address_dialog.py | 6 ++---- electrum/gui/qt/address_list.py | 6 +++++- electrum/gui/qt/amountedit.py | 4 ++-- electrum/gui/qt/completion_text_edit.py | 6 +++--- electrum/gui/qt/console.py | 9 +-------- electrum/gui/qt/contact_list.py | 4 ++-- electrum/gui/qt/exception_window.py | 4 ++-- electrum/gui/qt/fee_slider.py | 4 ++-- electrum/gui/qt/history_list.py | 12 +++++++++++- electrum/gui/qt/installwizard.py | 11 +++++++---- electrum/gui/qt/invoice_list.py | 7 ++++++- electrum/gui/qt/main_window.py | 19 ++++++++++++++----- electrum/gui/qt/network_dialog.py | 11 ++++++----- electrum/gui/qt/password_dialog.py | 6 +++--- electrum/gui/qt/paytoedit.py | 2 +- electrum/gui/qt/qrcodewidget.py | 3 +-- electrum/gui/qt/qrtextedit.py | 2 -- electrum/gui/qt/seed_dialog.py | 8 +++++++- electrum/gui/qt/transaction_dialog.py | 13 ++++++++----- electrum/gui/qt/util.py | 13 ++++++++++--- electrum/gui/qt/utxo_list.py | 6 +++++- electrum/plugins/audio_modem/qt.py | 6 ++---- electrum/plugins/coldcard/qt.py | 6 +++++- electrum/plugins/cosigner_pool/qt.py | 3 +-- electrum/plugins/email_requests/qt.py | 3 +-- electrum/plugins/hw_wallet/qt.py | 7 +++++-- electrum/plugins/keepkey/qt.py | 12 ++++++++---- electrum/plugins/labels/qt.py | 7 ++----- electrum/plugins/ledger/auth2fa.py | 9 ++++----- electrum/plugins/ledger/qt.py | 7 ++++++- electrum/plugins/revealer/qt.py | 16 ++++++++++++---- electrum/plugins/safe_t/qt.py | 12 ++++++++---- electrum/plugins/trezor/qt.py | 11 +++++++---- electrum/plugins/trustedcoin/qt.py | 16 +++++++++------- electrum/plugins/virtualkeyboard/qt.py | 5 +++-- electrum/rsakey.py | 2 -- electrum/wallet.py | 1 - 38 files changed, 177 insertions(+), 113 deletions(-) diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 50e914aa..54863a22 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -23,6 +23,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import os import signal import sys import traceback @@ -34,14 +35,14 @@ try: except Exception: sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'") -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QGuiApplication +from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QWidget, QMenu, + QMessageBox) +from PyQt5.QtCore import QObject, pyqtSignal, QTimer import PyQt5.QtCore as QtCore from electrum.i18n import _, set_language from electrum.plugin import run_hook -from electrum.storage import WalletStorage from electrum.base_wizard import GoBack from electrum.util import (UserCancelled, PrintError, profiler, WalletFileException, BitcoinException, get_new_wallet_name) @@ -49,7 +50,7 @@ from electrum.util import (UserCancelled, PrintError, profiler, from .installwizard import InstallWizard -from .util import * # * needed for plugins +from .util import get_default_language, read_QIcon, ColorScheme from .main_window import ElectrumWindow from .network_dialog import NetworkDialog diff --git a/electrum/gui/qt/address_dialog.py b/electrum/gui/qt/address_dialog.py index 8dfb220a..083d5119 100644 --- a/electrum/gui/qt/address_dialog.py +++ b/electrum/gui/qt/address_dialog.py @@ -25,11 +25,9 @@ from electrum.i18n import _ -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * +from PyQt5.QtWidgets import QVBoxLayout, QLabel -from .util import * +from .util import WindowModalDialog, ButtonsLineEdit, ColorScheme, Buttons, CloseButton from .history_list import HistoryList, HistoryModel from .qrtextedit import ShowQRTextEdit diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py index a29e3196..815b098f 100644 --- a/electrum/gui/qt/address_list.py +++ b/electrum/gui/qt/address_list.py @@ -26,13 +26,17 @@ import webbrowser from enum import IntEnum +from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex +from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont +from PyQt5.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu + from electrum.i18n import _ from electrum.util import block_explorer_URL from electrum.plugin import run_hook from electrum.bitcoin import is_address from electrum.wallet import InternalAddressCorruption -from .util import * +from .util import MyTreeView, MONOSPACE_FONT, ColorScheme class AddressList(MyTreeView): diff --git a/electrum/gui/qt/amountedit.py b/electrum/gui/qt/amountedit.py index 7d802a4b..c3920b9d 100644 --- a/electrum/gui/qt/amountedit.py +++ b/electrum/gui/qt/amountedit.py @@ -2,8 +2,8 @@ from decimal import Decimal -from PyQt5.QtCore import * -from PyQt5.QtGui import * +from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtGui import QPalette, QPainter from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame) from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name, diff --git a/electrum/gui/qt/completion_text_edit.py b/electrum/gui/qt/completion_text_edit.py index ca6e6832..4a8f908b 100644 --- a/electrum/gui/qt/completion_text_edit.py +++ b/electrum/gui/qt/completion_text_edit.py @@ -23,9 +23,9 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from PyQt5.QtGui import * -from PyQt5.QtCore import * -from PyQt5.QtWidgets import * +from PyQt5.QtGui import QTextCursor +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QCompleter, QPlainTextEdit, QApplication from .util import ButtonsTextEdit diff --git a/electrum/gui/qt/console.py b/electrum/gui/qt/console.py index 96564b0f..0104147e 100644 --- a/electrum/gui/qt/console.py +++ b/electrum/gui/qt/console.py @@ -5,7 +5,6 @@ import sys import os import re import traceback -import platform from PyQt5 import QtCore from PyQt5 import QtGui @@ -14,13 +13,7 @@ from PyQt5 import QtWidgets from electrum import util from electrum.i18n import _ - -if platform.system() == 'Windows': - MONOSPACE_FONT = 'Lucida Console' -elif platform.system() == 'Darwin': - MONOSPACE_FONT = 'Monaco' -else: - MONOSPACE_FONT = 'monospace' +from .util import MONOSPACE_FONT class OverlayLabel(QtWidgets.QLabel): diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py index 9eb8b50c..017206d5 100644 --- a/electrum/gui/qt/contact_list.py +++ b/electrum/gui/qt/contact_list.py @@ -26,8 +26,8 @@ import webbrowser from enum import IntEnum -from PyQt5.QtGui import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QStandardItemModel, QStandardItem +from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex from PyQt5.QtWidgets import (QAbstractItemView, QMenu) from electrum.i18n import _ diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py index 3d3d650a..b14db0d2 100644 --- a/electrum/gui/qt/exception_window.py +++ b/electrum/gui/qt/exception_window.py @@ -27,8 +27,8 @@ import traceback from PyQt5.QtCore import QObject import PyQt5.QtCore as QtCore -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import * +from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit, + QMessageBox, QHBoxLayout, QVBoxLayout) from electrum.i18n import _ from electrum.base_crash_reporter import BaseCrashReporter diff --git a/electrum/gui/qt/fee_slider.py b/electrum/gui/qt/fee_slider.py index 019412b6..25be72b8 100644 --- a/electrum/gui/qt/fee_slider.py +++ b/electrum/gui/qt/fee_slider.py @@ -1,7 +1,7 @@ import threading -from PyQt5.QtGui import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QCursor +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QSlider, QToolTip from electrum.i18n import _ diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index 950e44c6..58d7bbf0 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -23,6 +23,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import os import webbrowser import datetime from datetime import date @@ -31,12 +32,21 @@ import threading from enum import IntEnum from decimal import Decimal +from PyQt5.QtGui import QMouseEvent, QFont, QBrush, QColor +from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, QAbstractItemModel, + QSortFilterProxyModel, QVariant, QItemSelectionModel, QDate, QPoint) +from PyQt5.QtWidgets import (QMenu, QHeaderView, QLabel, QMessageBox, + QPushButton, QComboBox, QVBoxLayout, QCalendarWidget, + QGridLayout) + from electrum.address_synchronizer import TX_HEIGHT_LOCAL from electrum.i18n import _ from electrum.util import (block_explorer_URL, profiler, print_error, TxMinedInfo, OrderedDictWithIndex, PrintError, timestamp_to_datetime) -from .util import * +from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton, + filename_field, MyTreeView, AcceptFileDragDrop, WindowModalDialog, + CloseButton) if TYPE_CHECKING: from electrum.wallet import Abstract_Wallet diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py index 5b30a8a9..ad2403a2 100644 --- a/electrum/gui/qt/installwizard.py +++ b/electrum/gui/qt/installwizard.py @@ -8,9 +8,11 @@ import threading import traceback from typing import Tuple, List, Callable -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * +from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal +from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap +from PyQt5.QtWidgets import (QWidget, QDialog, QLabel, QHBoxLayout, QMessageBox, + QVBoxLayout, QLineEdit, QFileDialog, QPushButton, + QGridLayout, QSlider, QScrollArea) from electrum.wallet import Wallet from electrum.storage import WalletStorage @@ -20,7 +22,8 @@ from electrum.i18n import _ from .seed_dialog import SeedLayout, KeysLayout from .network_dialog import NetworkChoiceLayout -from .util import * +from .util import (MessageBoxMixin, Buttons, icon_path, ChoicesLayout, WWLabel, + InfoButton) from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py index f011d6bb..358f36fa 100644 --- a/electrum/gui/qt/invoice_list.py +++ b/electrum/gui/qt/invoice_list.py @@ -25,10 +25,15 @@ from enum import IntEnum +from PyQt5.QtCore import Qt, QItemSelectionModel +from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont +from PyQt5.QtWidgets import QHeaderView, QMenu + from electrum.i18n import _ from electrum.util import format_time -from .util import * +from .util import (MyTreeView, read_QIcon, MONOSPACE_FONT, PR_UNPAID, + pr_tooltips, import_meta_gui, export_meta_gui, pr_icons) class InvoiceList(MyTreeView): diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 38298239..54ebfe4f 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -38,10 +38,14 @@ from functools import partial import queue import asyncio -from PyQt5.QtGui import * -from PyQt5.QtCore import * -import PyQt5.QtCore as QtCore -from PyQt5.QtWidgets import * +from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor +from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal +from PyQt5.QtWidgets import (QMessageBox, QComboBox, QSystemTrayIcon, QTabWidget, + QSpinBox, QMenuBar, QFileDialog, QCheckBox, QLabel, + QVBoxLayout, QGridLayout, QLineEdit, QTreeWidgetItem, + QHBoxLayout, QPushButton, QScrollArea, QTextEdit, + QShortcut, QMainWindow, QCompleter, QInputDialog, + QWidget, QMenu, QSizePolicy, QStatusBar) import electrum from electrum import (keystore, simple_config, ecc, constants, util, bitcoin, commands, @@ -72,7 +76,12 @@ from .qrcodewidget import QRCodeWidget, QRDialog from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .transaction_dialog import show_transaction from .fee_slider import FeeSlider -from .util import * +from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog, + WindowModalDialog, ChoicesLayout, HelpLabel, FromList, Buttons, + OkButton, InfoButton, WWLabel, TaskThread, CancelButton, + CloseButton, HelpButton, MessageBoxMixin, EnterButton, expiration_values, + ButtonsLineEdit, CopyCloseButton, import_meta_gui, export_meta_gui, + filename_field, address_field) from .installwizard import WIF_HELP_TEXT from .history_list import HistoryList, HistoryModel from .update_checker import UpdateCheck, UpdateCheckThread diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 24a8a8b3..6d9f6224 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -24,12 +24,13 @@ # SOFTWARE. import socket +import time from enum import IntEnum -from PyQt5.QtGui import * -from PyQt5.QtCore import * -from PyQt5.QtWidgets import * -import PyQt5.QtCore as QtCore +from PyQt5.QtCore import Qt, pyqtSignal, QThread +from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, + QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox, + QTabWidget, QWidget, QLabel) from electrum.i18n import _ from electrum import constants, blockchain @@ -37,7 +38,7 @@ from electrum.util import print_error from electrum.interface import serialize_server, deserialize_server from electrum.network import Network -from .util import * +from .util import Buttons, CloseButton, HelpButton, read_QIcon protocol_names = ['TCP', 'SSL'] protocol_letters = 'ts' diff --git a/electrum/gui/qt/password_dialog.py b/electrum/gui/qt/password_dialog.py index 83862b92..851bbc5a 100644 --- a/electrum/gui/qt/password_dialog.py +++ b/electrum/gui/qt/password_dialog.py @@ -27,13 +27,13 @@ import re import math from PyQt5.QtCore import Qt -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox from electrum.i18n import _ from electrum.plugin import run_hook -from .util import * +from .util import icon_path, WindowModalDialog, OkButton, CancelButton, Buttons def check_password_strength(password): diff --git a/electrum/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py index 6235b85c..417833af 100644 --- a/electrum/gui/qt/paytoedit.py +++ b/electrum/gui/qt/paytoedit.py @@ -26,7 +26,7 @@ import re from decimal import Decimal -from PyQt5.QtGui import * +from PyQt5.QtGui import QFontMetrics from electrum import bitcoin from electrum.util import bfh, PrintError diff --git a/electrum/gui/qt/qrcodewidget.py b/electrum/gui/qt/qrcodewidget.py index 5071914c..aef2e8dc 100644 --- a/electrum/gui/qt/qrcodewidget.py +++ b/electrum/gui/qt/qrcodewidget.py @@ -1,8 +1,7 @@ import os import qrcode -from PyQt5.QtCore import * -from PyQt5.QtGui import * +from PyQt5.QtGui import QColor import PyQt5.QtGui as QtGui from PyQt5.QtWidgets import ( QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget) diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py index b84ea5d6..a7c08793 100644 --- a/electrum/gui/qt/qrtextedit.py +++ b/electrum/gui/qt/qrtextedit.py @@ -1,5 +1,3 @@ -from PyQt5.QtGui import * -from PyQt5.QtCore import * from PyQt5.QtWidgets import QFileDialog from electrum.i18n import _ diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index da3f332e..2b75c3c9 100644 --- a/electrum/gui/qt/seed_dialog.py +++ b/electrum/gui/qt/seed_dialog.py @@ -23,11 +23,17 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, + QLabel, QCompleter, QDialog) + from electrum.i18n import _ from electrum.mnemonic import Mnemonic import electrum.old_mnemonic -from .util import * +from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path, + EnterButton, CloseButton, WindowModalDialog) from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .completion_text_edit import CompletionTextEdit diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index 9178e5b5..570105ee 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -22,15 +22,17 @@ # 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. + +import sys import copy import datetime import json import traceback -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * - +from PyQt5.QtCore import QSize +from PyQt5.QtGui import QTextCharFormat, QBrush, QFont +from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, + QTextEdit) import qrcode from qrcode import exceptions @@ -41,7 +43,8 @@ from electrum import simple_config from electrum.util import bfh from electrum.transaction import SerializationError, Transaction -from .util import * +from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton, + MONOSPACE_FONT, ColorScheme, ButtonsLineEdit) SAVE_BUTTON_ENABLED_TOOLTIP = _("Save transaction offline") diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index d8c2c153..72439f6f 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -9,9 +9,16 @@ import traceback from functools import partial, lru_cache from typing import NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict -from PyQt5.QtGui import * -from PyQt5.QtCore import * -from PyQt5.QtWidgets import * +from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, + QPalette, QIcon) +from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal, + QCoreApplication, QItemSelectionModel, QThread, + QSortFilterProxyModel, QSize, QLocale) +from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, + QAbstractItemView, QVBoxLayout, QLineEdit, + QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, + QFileDialog, QWidget, QToolButton, QTreeView, QPlainTextEdit, + QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate) from electrum.i18n import _, languages from electrum.util import (FileImportFailed, FileExportFailed, diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 529e6d12..6e8fbf97 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -26,9 +26,13 @@ from typing import Optional, List from enum import IntEnum +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont +from PyQt5.QtWidgets import QAbstractItemView, QMenu + from electrum.i18n import _ -from .util import * +from .util import MyTreeView, ColorScheme, MONOSPACE_FONT class UTXOList(MyTreeView): diff --git a/electrum/plugins/audio_modem/qt.py b/electrum/plugins/audio_modem/qt.py index 36ba100e..27bbd4a5 100644 --- a/electrum/plugins/audio_modem/qt.py +++ b/electrum/plugins/audio_modem/qt.py @@ -5,15 +5,13 @@ from io import BytesIO import sys import platform +from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) + from electrum.plugin import BasePlugin, hook from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon from electrum.util import print_msg, print_error from electrum.i18n import _ -from PyQt5.QtGui import * -from PyQt5.QtCore import * -from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) - try: import amodem.audio import amodem.main diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py index a3409822..28801f43 100644 --- a/electrum/plugins/coldcard/qt.py +++ b/electrum/plugins/coldcard/qt.py @@ -1,9 +1,13 @@ import time +from functools import partial + +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout from electrum.i18n import _ from electrum.plugin import hook from electrum.wallet import Standard_Wallet -from electrum.gui.qt.util import * +from electrum.gui.qt.util import WindowModalDialog, CloseButton, get_parent_main_window from .coldcard import ColdcardPlugin from ..hw_wallet.qt import QtHandlerBase, QtPluginBase diff --git a/electrum/plugins/cosigner_pool/qt.py b/electrum/plugins/cosigner_pool/qt.py index 175e469c..cd10407c 100644 --- a/electrum/plugins/cosigner_pool/qt.py +++ b/electrum/plugins/cosigner_pool/qt.py @@ -26,8 +26,7 @@ import time from xmlrpc.client import ServerProxy -from PyQt5.QtGui import * -from PyQt5.QtCore import * +from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtWidgets import QPushButton from electrum import util, keystore, ecc, bip32, crypto diff --git a/electrum/plugins/email_requests/qt.py b/electrum/plugins/email_requests/qt.py index 93112142..b46c01ee 100644 --- a/electrum/plugins/email_requests/qt.py +++ b/electrum/plugins/email_requests/qt.py @@ -37,8 +37,7 @@ from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.encoders import encode_base64 -from PyQt5.QtGui import * -from PyQt5.QtCore import * +from PyQt5.QtCore import QObject, pyqtSignal, QThread from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit, QInputDialog) diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index 2c5733ee..e120ea98 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -25,11 +25,14 @@ # SOFTWARE. import threading +from functools import partial -from PyQt5.QtWidgets import QVBoxLayout, QLabel +from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE -from electrum.gui.qt.util import * +from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog, + Buttons, CancelButton, TaskThread) from electrum.i18n import _ from electrum.util import PrintError diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index e5f5b6a4..3d378020 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -1,11 +1,15 @@ from functools import partial import threading -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton -from PyQt5.QtWidgets import QVBoxLayout, QLabel +from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal, QRegExp +from PyQt5.QtGui import QRegExpValidator +from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, + QHBoxLayout, QButtonGroup, QGroupBox, QDialog, + QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, + QMessageBox, QFileDialog, QSlider, QTabWidget) -from electrum.gui.qt.util import * +from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, + OkButton, CloseButton) from electrum.i18n import _ from electrum.plugin import hook from electrum.util import bh2u diff --git a/electrum/plugins/labels/qt.py b/electrum/plugins/labels/qt.py index d834b7a3..e3b83950 100644 --- a/electrum/plugins/labels/qt.py +++ b/electrum/plugins/labels/qt.py @@ -2,15 +2,12 @@ from functools import partial import traceback import sys -from PyQt5.QtGui import * -from PyQt5.QtCore import * +from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout) from electrum.plugin import hook from electrum.i18n import _ -from electrum.gui.qt import EnterButton -from electrum.gui.qt.util import ThreadedButton, Buttons -from electrum.gui.qt.util import WindowModalDialog, OkButton +from electrum.gui.qt.util import ThreadedButton, Buttons, EnterButton, WindowModalDialog, OkButton from .labels import LabelsPlugin diff --git a/electrum/plugins/ledger/auth2fa.py b/electrum/plugins/ledger/auth2fa.py index fbc44e14..dc8c5a35 100644 --- a/electrum/plugins/ledger/auth2fa.py +++ b/electrum/plugins/ledger/auth2fa.py @@ -7,17 +7,16 @@ from binascii import hexlify, unhexlify import websocket -from PyQt5.QtWidgets import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel -import PyQt5.QtCore as QtCore -from PyQt5.QtWidgets import * +from PyQt5.QtWidgets import (QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel, + QWidget, QHBoxLayout, QComboBox, QPushButton) +from PyQt5.QtCore import QThread, pyqtSignal -from btchip.btchip import * +from btchip.btchip import BTChipException from electrum.i18n import _ from electrum.util import print_msg from electrum import constants, bitcoin from electrum.gui.qt.qrcodewidget import QRCodeWidget -from electrum.gui.qt.util import * DEBUG = False diff --git a/electrum/plugins/ledger/qt.py b/electrum/plugins/ledger/qt.py index c7cd8b4d..b2e5bc00 100644 --- a/electrum/plugins/ledger/qt.py +++ b/electrum/plugins/ledger/qt.py @@ -1,9 +1,14 @@ +from functools import partial + #from btchip.btchipPersoWizard import StartBTChipPersoDialog +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtWidgets import QInputDialog, QLabel, QVBoxLayout, QLineEdit + from electrum.i18n import _ from electrum.plugin import hook from electrum.wallet import Standard_Wallet -from electrum.gui.qt.util import * +from electrum.gui.qt.util import WindowModalDialog from .ledger import LedgerPlugin from ..hw_wallet.qt import QtHandlerBase, QtPluginBase diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py index fb67dae2..594bb4e3 100644 --- a/electrum/plugins/revealer/qt.py +++ b/electrum/plugins/revealer/qt.py @@ -11,20 +11,28 @@ Tiago Romagnani Silveira, 2017 import os import random -import qrcode import traceback from decimal import Decimal +from functools import partial +import sys +import qrcode from PyQt5.QtPrintSupport import QPrinter +from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize +from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, + QColor, QDesktopServices, qRgba, QPainterPath) +from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QLineEdit) from electrum.plugin import hook from electrum.i18n import _ -from electrum.util import make_dir, InvalidPassword, UserCancelled, bh2u, bfh -from electrum.gui.qt.util import * +from electrum.util import make_dir, InvalidPassword, UserCancelled +from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path, + WindowModalDialog, Buttons, CloseButton, OkButton) from electrum.gui.qt.qrtextedit import ScanQRTextEdit from electrum.gui.qt.main_window import StatusBarButton -from .revealer import RevealerPlugin, VersionedSeed +from .revealer import RevealerPlugin class Plugin(RevealerPlugin): diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index 88931bea..6531f549 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -1,11 +1,15 @@ from functools import partial import threading -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton -from PyQt5.QtWidgets import QVBoxLayout, QLabel +from PyQt5.QtCore import Qt, pyqtSignal, QRegExp +from PyQt5.QtGui import QRegExpValidator +from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, + QHBoxLayout, QButtonGroup, QGroupBox, + QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, + QMessageBox, QFileDialog, QSlider, QTabWidget) -from electrum.gui.qt.util import * +from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, + OkButton, CloseButton) from electrum.i18n import _ from electrum.plugin import hook from electrum.util import bh2u diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index b45d8325..c94bba35 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -1,11 +1,14 @@ from functools import partial import threading -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGridLayout, QInputDialog, QPushButton -from PyQt5.QtWidgets import QVBoxLayout, QLabel +from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal +from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, + QHBoxLayout, QButtonGroup, QGroupBox, QDialog, + QLineEdit, QRadioButton, QCheckBox, QWidget, + QMessageBox, QFileDialog, QSlider, QTabWidget) -from electrum.gui.qt.util import * +from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, + OkButton, CloseButton) from electrum.i18n import _ from electrum.plugin import hook from electrum.util import bh2u diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py index 34446fe2..72cd5f45 100644 --- a/electrum/plugins/trustedcoin/qt.py +++ b/electrum/plugins/trustedcoin/qt.py @@ -25,14 +25,16 @@ from functools import partial import threading -from threading import Thread -import re -from decimal import Decimal +import sys +import os -from PyQt5.QtGui import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QPixmap +from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, + QRadioButton, QCheckBox, QLineEdit) -from electrum.gui.qt.util import * +from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton, + CancelButton, Buttons, icon_path, WWLabel, CloseButton) from electrum.gui.qt.qrcodewidget import QRCodeWidget from electrum.gui.qt.amountedit import AmountEdit from electrum.gui.qt.main_window import StatusBarButton @@ -254,7 +256,7 @@ class Plugin(TrustedCoinPlugin): tos_e.tos_signal.connect(on_result) tos_e.error_signal.connect(on_error) - t = Thread(target=request_TOS) + t = threading.Thread(target=request_TOS) t.setDaemon(True) t.start() email_e.textChanged.connect(set_enabled) diff --git a/electrum/plugins/virtualkeyboard/qt.py b/electrum/plugins/virtualkeyboard/qt.py index 3ce78af3..3016a77c 100644 --- a/electrum/plugins/virtualkeyboard/qt.py +++ b/electrum/plugins/virtualkeyboard/qt.py @@ -1,8 +1,9 @@ -from PyQt5.QtGui import * +import random + from PyQt5.QtWidgets import (QVBoxLayout, QGridLayout, QPushButton) + from electrum.plugin import BasePlugin, hook from electrum.i18n import _ -import random class Plugin(BasePlugin): diff --git a/electrum/rsakey.py b/electrum/rsakey.py index 18a0d733..86255ef2 100644 --- a/electrum/rsakey.py +++ b/electrum/rsakey.py @@ -37,8 +37,6 @@ import os import math import hashlib -from .pem import * - def SHA1(x): return hashlib.sha1(x).digest() diff --git a/electrum/wallet.py b/electrum/wallet.py index 03ca9049..b4b588c6 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -48,7 +48,6 @@ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, Fiat, bfh, bh2u, TxMinedInfo) from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script, is_minikey, relayfee, dust_threshold) -from .version import * from .crypto import sha256d from .keystore import load_keystore, Hardware_KeyStore from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW, WalletStorage From fd62ba874bf0dbb8b27df2bb4f554b700ca58963 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 11 Feb 2019 20:22:03 +0100 Subject: [PATCH 60/78] kivy: rm dead code --- electrum/gui/kivy/main_window.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index 09b1dbbf..9da231fe 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -829,9 +829,6 @@ class ElectrumWindow(App): self._clipboard.copy(label.data) Clock.schedule_once(lambda dt: self.show_info(_('Text copied to clipboard.\nTap again to display it as QR code.'))) - def set_send(self, address, amount, label, message): - self.send_payment(address, amount=amount, label=label, message=message) - def show_error(self, error, width='200dp', pos=None, arrow_pos=None, exit=False, icon='atlas://electrum/gui/kivy/theming/light/error', duration=0, modal=False): From 38ab7ee554b89b96c5ac7ea1b83d275d6cdb3cad Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 12 Feb 2019 17:02:15 +0100 Subject: [PATCH 61/78] network: catch untrusted exceptions from server in public methods and re-raise a wrapper exception (that retains the original exc in a field) closes #5111 --- electrum/network.py | 44 +++++++++++++++++++++++++++++++++++-- electrum/tests/test_util.py | 13 ++++++++++- electrum/util.py | 20 +++++++++++++++++ electrum/verifier.py | 5 ++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/electrum/network.py b/electrum/network.py index 5537c5c6..d13d3cf0 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -43,7 +43,8 @@ from aiohttp import ClientResponse from . import util from .util import (PrintError, print_error, log_exceptions, ignore_exceptions, - bfh, SilentTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter) + bfh, SilentTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter, + is_hash256_str, is_non_negative_integer) from .bitcoin import COIN from . import constants @@ -195,6 +196,17 @@ class TxBroadcastUnknownError(TxBroadcastError): _("Consider trying to connect to a different server, or updating Electrum.")) +class UntrustedServerReturnedError(Exception): + def __init__(self, *, original_exception): + self.original_exception = original_exception + + def __str__(self): + return _("The server returned an error.") + + def __repr__(self): + return f"" + + INSTANCE = None @@ -760,8 +772,21 @@ class Network(PrintError): raise BestEffortRequestFailed('no interface to do request on... gave up.') return make_reliable_wrapper + def catch_server_exceptions(func): + async def wrapper(self, *args, **kwargs): + try: + await func(self, *args, **kwargs) + except aiorpcx.jsonrpc.CodeMessageError as e: + raise UntrustedServerReturnedError(original_exception=e) + return wrapper + @best_effort_reliable + @catch_server_exceptions async def get_merkle_for_transaction(self, tx_hash: str, tx_height: int) -> dict: + if not is_hash256_str(tx_hash): + raise Exception(f"{repr(tx_hash)} is not a txid") + if not is_non_negative_integer(tx_height): + raise Exception(f"{repr(tx_height)} is not a block height") return await self.interface.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height]) @best_effort_reliable @@ -919,24 +944,39 @@ class Network(PrintError): return _("Unknown error") @best_effort_reliable - async def request_chunk(self, height, tip=None, *, can_return_early=False): + @catch_server_exceptions + async def request_chunk(self, height: int, tip=None, *, can_return_early=False): + if not is_non_negative_integer(height): + raise Exception(f"{repr(height)} is not a block height") return await self.interface.request_chunk(height, tip=tip, can_return_early=can_return_early) @best_effort_reliable + @catch_server_exceptions async def get_transaction(self, tx_hash: str, *, timeout=None) -> str: + if not is_hash256_str(tx_hash): + raise Exception(f"{repr(tx_hash)} is not a txid") return await self.interface.session.send_request('blockchain.transaction.get', [tx_hash], timeout=timeout) @best_effort_reliable + @catch_server_exceptions async def get_history_for_scripthash(self, sh: str) -> List[dict]: + if not is_hash256_str(sh): + raise Exception(f"{repr(sh)} is not a scripthash") return await self.interface.session.send_request('blockchain.scripthash.get_history', [sh]) @best_effort_reliable + @catch_server_exceptions async def listunspent_for_scripthash(self, sh: str) -> List[dict]: + if not is_hash256_str(sh): + raise Exception(f"{repr(sh)} is not a scripthash") return await self.interface.session.send_request('blockchain.scripthash.listunspent', [sh]) @best_effort_reliable + @catch_server_exceptions async def get_balance_for_scripthash(self, sh: str) -> dict: + if not is_hash256_str(sh): + raise Exception(f"{repr(sh)} is not a scripthash") return await self.interface.session.send_request('blockchain.scripthash.get_balance', [sh]) def blockchain(self) -> Blockchain: diff --git a/electrum/tests/test_util.py b/electrum/tests/test_util.py index b9436c7f..29bae112 100644 --- a/electrum/tests/test_util.py +++ b/electrum/tests/test_util.py @@ -1,6 +1,7 @@ from decimal import Decimal -from electrum.util import format_satoshis, format_fee_satoshis, parse_URI +from electrum.util import (format_satoshis, format_fee_satoshis, parse_URI, + is_hash256_str) from . import SequentialTestCase @@ -93,3 +94,13 @@ class TestUtil(SequentialTestCase): def test_parse_URI_parameter_polution(self): self.assertRaises(Exception, parse_URI, 'bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003&label=test&amount=30.0') + + def test_is_hash256_str(self): + self.assertTrue(is_hash256_str('09a4c03e3bdf83bbe3955f907ee52da4fc12f4813d459bc75228b64ad08617c7')) + self.assertTrue(is_hash256_str('2A5C3F4062E4F2FCCE7A1C7B4310CB647B327409F580F4ED72CB8FC0B1804DFA')) + self.assertTrue(is_hash256_str('00' * 32)) + + self.assertFalse(is_hash256_str('00' * 33)) + self.assertFalse(is_hash256_str('qweqwe')) + self.assertFalse(is_hash256_str(None)) + self.assertFalse(is_hash256_str(7)) diff --git a/electrum/util.py b/electrum/util.py index aa40c65a..638112ed 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -506,6 +506,26 @@ def is_valid_email(s): return re.match(regexp, s) is not None +def is_hash256_str(text: str) -> bool: + if not isinstance(text, str): return False + if len(text) != 64: return False + try: + bytes.fromhex(text) + except: + return False + return True + + +def is_non_negative_integer(val) -> bool: + try: + val = int(val) + if val >= 0: + return True + except: + pass + return False + + def format_satoshis_plain(x, decimal_point = 8): """Display a satoshi amount scaled. Always uses a '.' as a decimal point and has no thousands separator""" diff --git a/electrum/verifier.py b/electrum/verifier.py index a4c220cc..ba1eaec9 100644 --- a/electrum/verifier.py +++ b/electrum/verifier.py @@ -32,6 +32,7 @@ from .bitcoin import hash_decode, hash_encode from .transaction import Transaction from .blockchain import hash_header from .interface import GracefulDisconnect +from .network import UntrustedServerReturnedError from . import constants if TYPE_CHECKING: @@ -96,7 +97,9 @@ class SPV(NetworkJobOnDefaultServer): async def _request_and_verify_single_proof(self, tx_hash, tx_height): try: merkle = await self.network.get_merkle_for_transaction(tx_hash, tx_height) - except aiorpcx.jsonrpc.RPCError as e: + except UntrustedServerReturnedError as e: + if not isinstance(e.original_exception, aiorpcx.jsonrpc.RPCError): + raise self.print_error('tx {} not at height {}'.format(tx_hash, tx_height)) self.wallet.remove_unverified_tx(tx_hash, tx_height) try: self.requested_merkle.remove(tx_hash) From 2174fc0676aa24797fc5a5c635c14d9d285a5374 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 12 Feb 2019 18:38:35 +0100 Subject: [PATCH 62/78] cli history: add option to filter by block height --- electrum/commands.py | 11 +++++++++-- electrum/wallet.py | 13 +++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 07ec6a5f..6524a01b 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -537,11 +537,14 @@ class Commands: return tx.as_dict() @command('w') - def history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False): + def history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False, + from_height=None, to_height=None): """Wallet history. Returns the transaction history of your wallet.""" kwargs = { 'show_addresses': show_addresses, 'show_fees': show_fees, + 'from_height': from_height, + 'to_height': to_height, } if year: import time @@ -831,7 +834,9 @@ command_options = { 'show_fees': (None, "Show miner fees paid by transactions"), 'year': (None, "Show history for a given year"), 'fee_method': (None, "Fee estimation method to use"), - 'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position") + 'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position"), + 'from_height': (None, "Only show transactions that confirmed after given block height"), + 'to_height': (None, "Only show transactions that confirmed before given block height"), } @@ -843,6 +848,8 @@ arg_types = { 'nbits': int, 'imax': int, 'year': int, + 'from_height': int, + 'to_height': int, 'tx': tx_from_str, 'pubkeys': json_loads, 'jsontx': json_loads, diff --git a/electrum/wallet.py b/electrum/wallet.py index b4b588c6..b1cac408 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -422,7 +422,11 @@ class Abstract_Wallet(AddressSynchronizer): @profiler def get_full_history(self, domain=None, from_timestamp=None, to_timestamp=None, - fx=None, show_addresses=False, show_fees=False): + fx=None, show_addresses=False, show_fees=False, + from_height=None, to_height=None): + if (from_timestamp is not None or to_timestamp is not None) \ + and (from_height is not None or to_height is not None): + raise Exception('timestamp and block height based filtering cannot be used together') out = [] income = 0 expenditures = 0 @@ -437,10 +441,15 @@ class Abstract_Wallet(AddressSynchronizer): continue if to_timestamp and (timestamp or now) >= to_timestamp: continue + height = tx_mined_status.height + if from_height is not None and height < from_height: + continue + if to_height is not None and height >= to_height: + continue tx = self.transactions.get(tx_hash) item = { 'txid': tx_hash, - 'height': tx_mined_status.height, + 'height': height, 'confirmations': tx_mined_status.conf, 'timestamp': timestamp, 'incoming': True if value>0 else False, From 019884a98b3bf615f2f02bbbdd4512b55d765ebc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 12 Feb 2019 19:23:58 +0100 Subject: [PATCH 63/78] network: follow-up 38ab7ee554b89b96c5ac7ea1b83d275d6cdb3cad --- electrum/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/network.py b/electrum/network.py index d13d3cf0..d663cf10 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -775,7 +775,7 @@ class Network(PrintError): def catch_server_exceptions(func): async def wrapper(self, *args, **kwargs): try: - await func(self, *args, **kwargs) + return await func(self, *args, **kwargs) except aiorpcx.jsonrpc.CodeMessageError as e: raise UntrustedServerReturnedError(original_exception=e) return wrapper From 086372f68aaa56bc426eba0b9087b3f4e2e1a8c2 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 12 Feb 2019 19:38:15 +0100 Subject: [PATCH 64/78] wallet get_full_history: add from/to_height info to summary --- electrum/wallet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electrum/wallet.py b/electrum/wallet.py index b1cac408..fe3877c2 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -500,6 +500,8 @@ class Abstract_Wallet(AddressSynchronizer): summary = { 'start_date': start_date, 'end_date': end_date, + 'from_height': from_height, + 'to_height': to_height, 'start_balance': Satoshis(start_balance), 'end_balance': Satoshis(end_balance), 'income': Satoshis(income), From c8562f53622309a736c0be51449db13b3a403d3e Mon Sep 17 00:00:00 2001 From: ghost43 Date: Tue, 12 Feb 2019 20:23:43 +0100 Subject: [PATCH 65/78] network: reintroduce network.debug (#5093) network.debug and interface.debug were removed during the asyncio-aiorpcx network-rewrite. --- electrum/interface.py | 26 ++++++++++++++++++++++++-- electrum/network.py | 3 +++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/electrum/interface.py b/electrum/interface.py index 7a943215..8ac90adb 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -71,8 +71,16 @@ class NotificationSession(RPCSession): self.cache = {} self.in_flight_requests_semaphore = asyncio.Semaphore(100) self.default_timeout = NetworkTimeout.Generic.NORMAL + self._msg_counter = 0 + self.interface = None # type: Optional[Interface] + + def _get_and_inc_msg_counter(self): + # runs in event loop thread, no need for lock + self._msg_counter += 1 + return self._msg_counter async def handle_request(self, request): + self.maybe_log(f"--> {request}") # note: if server sends malformed request and we raise, the superclass # will catch the exception, count errors, and at some point disconnect if isinstance(request, Notification): @@ -91,12 +99,17 @@ class NotificationSession(RPCSession): timeout = self.default_timeout # note: the semaphore implementation guarantees no starvation async with self.in_flight_requests_semaphore: + msg_id = self._get_and_inc_msg_counter() + self.maybe_log(f"<-- {args} {kwargs} (id: {msg_id})") try: - return await asyncio.wait_for( + response = await asyncio.wait_for( super().send_request(*args, **kwargs), timeout) except asyncio.TimeoutError as e: - raise RequestTimedOut('request timed out: {}'.format(args)) from e + raise RequestTimedOut(f'request timed out: {args} (id: {msg_id})') from e + else: + self.maybe_log(f"--> {response} (id: {msg_id})") + return response async def subscribe(self, method: str, params: List, queue: asyncio.Queue): # note: until the cache is written for the first time, @@ -123,6 +136,11 @@ class NotificationSession(RPCSession): """Hashable index for subscriptions and cache""" return str(method) + repr(params) + def maybe_log(self, msg: str) -> None: + if not self.interface: return + if self.interface.debug or self.interface.network.debug: + self.interface.print_error(msg) + class GracefulDisconnect(Exception): pass @@ -173,6 +191,9 @@ class Interface(PrintError): self.tip_header = None self.tip = 0 + # Dump network messages (only for this interface). Set at runtime from the console. + self.debug = False + asyncio.run_coroutine_threadsafe( self.network.main_taskgroup.spawn(self.run()), self.network.asyncio_loop) self.group = SilentTaskGroup() @@ -370,6 +391,7 @@ class Interface(PrintError): host=self.host, port=self.port, ssl=sslc, proxy=self.proxy) as session: self.session = session # type: NotificationSession + self.session.interface = self self.session.default_timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Generic) try: ver = await session.send_request('server.version', [version.ELECTRUM_VERSION, version.PROTOCOL_VERSION]) diff --git a/electrum/network.py b/electrum/network.py index d663cf10..030c50c6 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -276,6 +276,9 @@ class Network(PrintError): self.server_queue = None self.proxy = None + # Dump network messages (all interfaces). Set at runtime from the console. + self.debug = False + self._set_status('disconnected') def run_from_another_thread(self, coro): From e2eb051eed67d0c8f404fe69d94b28b33f8d6d98 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 13 Feb 2019 15:03:32 +0100 Subject: [PATCH 66/78] keystore bip39: minor clean-up --- electrum/keystore.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/electrum/keystore.py b/electrum/keystore.py index 0b857527..a2430c54 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -26,7 +26,7 @@ from unicodedata import normalize import hashlib -import binascii +from typing import Tuple from . import bitcoin, ecc, constants, bip32 from .bitcoin import (deserialize_privkey, serialize_privkey, @@ -601,14 +601,15 @@ def bip39_to_seed(mnemonic, passphrase): return hashlib.pbkdf2_hmac('sha512', mnemonic.encode('utf-8'), b'mnemonic' + passphrase.encode('utf-8'), iterations = PBKDF2_ROUNDS) -# returns tuple (is_checksum_valid, is_wordlist_valid) -def bip39_is_checksum_valid(mnemonic): + +def bip39_is_checksum_valid(mnemonic: str) -> Tuple[bool, bool]: + """Test checksum of bip39 mnemonic assuming English wordlist. + Returns tuple (is_checksum_valid, is_wordlist_valid) + """ words = [ normalize('NFKD', word) for word in mnemonic.split() ] words_len = len(words) wordlist = load_wordlist("english.txt") n = len(wordlist) - checksum_length = 11*words_len//33 - entropy_length = 32*checksum_length i = 0 words.reverse() while words: @@ -620,13 +621,12 @@ def bip39_is_checksum_valid(mnemonic): i = i*n + k if words_len not in [12, 15, 18, 21, 24]: return False, True + checksum_length = 11 * words_len // 33 # num bits + entropy_length = 32 * checksum_length # num bits entropy = i >> checksum_length checksum = i % 2**checksum_length - h = '{:x}'.format(entropy) - while len(h) < entropy_length/4: - h = '0'+h - b = bytearray.fromhex(h) - hashed = int(binascii.hexlify(sha256(b)), 16) + entropy_bytes = int.to_bytes(entropy, length=entropy_length//8, byteorder="big") + hashed = int.from_bytes(sha256(entropy_bytes), byteorder="big") calculated_checksum = hashed >> (256 - checksum_length) return checksum == calculated_checksum, True From ec86850a2e7caa16d96e08c090e98d3b6be4ad1a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 13 Feb 2019 18:33:48 +0100 Subject: [PATCH 67/78] trezor: fix minor error handling issue AttributeError: 'TrezorFailure' object has no attribute 'message' --- electrum/plugins/trezor/clientbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py index e31abaa1..3ec43d5c 100644 --- a/electrum/plugins/trezor/clientbase.py +++ b/electrum/plugins/trezor/clientbase.py @@ -65,7 +65,7 @@ class TrezorClientBase(PrintError): if issubclass(exc_type, Cancelled): raise UserCancelled from exc_value elif issubclass(exc_type, TrezorFailure): - raise RuntimeError(exc_value.message) from exc_value + raise RuntimeError(str(exc_value)) from exc_value elif issubclass(exc_type, OutdatedFirmwareError): raise UserFacingException(exc_value) from exc_value else: From 2867c2ef7ab82d347b3b48df239f3b8685a925b0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 13 Feb 2019 20:23:54 +0100 Subject: [PATCH 68/78] update release notes --- RELEASE-NOTES | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 568fdecf..5dcf34d2 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,3 +1,19 @@ +# Release 3.3.4 - (unreleased) + + * AppImage: we now also distribute self-contained binaries for x86_64 + Linux in the form of an AppImage (#5042). The Python interpreter, + PyQt5, libsecp256k1, PyCryptodomex, zbar, hidapi/libusb (including + hardware wallet libraries) are all bundled. Note that users of + hw wallets still need to set udev rules themselves. + * hw wallets: fix a regression during transaction signing that prompts + the user too many times for confirmations (commit 2729909) + * transactions now set nVersion to 2, to mimic Bitcoin Core + * fix Qt bug that made all hw wallets unusable on Windows 8.1 (#4960) + * fix bugs in wallet creation wizard that resulted in corrupted + wallets being created in rare cases (#5082, #5057) + * fix compatibility with Qt 5.12 (#5109) + + # Release 3.3.3 - (January 25, 2019) * Do not expose users to server error messages (#4968) From 4e9dd679ab2fd8ccf3df6829772bb8a8a43d61c6 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 13 Feb 2019 21:46:48 +0100 Subject: [PATCH 69/78] add contrib/sign_version --- contrib/sign_version | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 contrib/sign_version diff --git a/contrib/sign_version b/contrib/sign_version new file mode 100755 index 00000000..f3bd40f1 --- /dev/null +++ b/contrib/sign_version @@ -0,0 +1,4 @@ +#!/bin/bash +version=`git describe --tags --dirty --always` +sig=`./run_electrum -w $SIGNING_WALLET signmessage $SIGNING_ADDRESS $version` +echo "{ \"version\":\"$version\", \"signatures\":{ \"$addr\":\"$sig\"}}" From 0e78e15fa4ae87e63bf3b226055e7d58fb527773 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 13 Feb 2019 22:20:56 +0100 Subject: [PATCH 70/78] update locale submodule --- contrib/deterministic-build/electrum-locale | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/deterministic-build/electrum-locale b/contrib/deterministic-build/electrum-locale index e5b28e47..ff5ad3a4 160000 --- a/contrib/deterministic-build/electrum-locale +++ b/contrib/deterministic-build/electrum-locale @@ -1 +1 @@ -Subproject commit e5b28e4717d8c8a736d6d4e5398fa4fa67db80d0 +Subproject commit ff5ad3a4436dddcc82799f8a91793013240c3b7b From 87c596fa1d685b9365c26b9dfabe9c566f806ea0 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 13 Feb 2019 22:23:30 +0100 Subject: [PATCH 71/78] prepare release 3.3.4 --- RELEASE-NOTES | 2 +- electrum/version.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 5dcf34d2..f7e37e79 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,4 +1,4 @@ -# Release 3.3.4 - (unreleased) +# Release 3.3.4 - (February 13, 2019) * AppImage: we now also distribute self-contained binaries for x86_64 Linux in the form of an AppImage (#5042). The Python interpreter, diff --git a/electrum/version.py b/electrum/version.py index a80d45f4..ef0927dd 100644 --- a/electrum/version.py +++ b/electrum/version.py @@ -1,5 +1,5 @@ -ELECTRUM_VERSION = '3.3.3' # version of the client package -APK_VERSION = '3.3.3.0' # read by buildozer.spec +ELECTRUM_VERSION = '3.3.4' # version of the client package +APK_VERSION = '3.3.4.0' # read by buildozer.spec PROTOCOL_VERSION = '1.4' # protocol version requested From c725c059474ec5e0b315082a910b58a6265120fb Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 13 Feb 2019 23:19:34 +0100 Subject: [PATCH 72/78] update make_download --- contrib/make_download | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/make_download b/contrib/make_download index 097fbf06..bafc8912 100755 --- a/contrib/make_download +++ b/contrib/make_download @@ -24,6 +24,7 @@ string = string.replace("##VERSION_APK##", APK_VERSION) files = { 'tgz': "Electrum-%s.tar.gz" % version, + 'appimage': "electrum-%s-x86_64.AppImage" % version, 'zip': "Electrum-%s.zip" % version, 'mac': "electrum-%s.dmg" % version_mac, 'win': "electrum-%s.exe" % version_win, From f60d1e7cb60c0193298ebd8c3ea8f9cdf8273312 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 13 Feb 2019 23:21:49 +0100 Subject: [PATCH 73/78] fix variable name in contrib/sign_version --- contrib/sign_version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/sign_version b/contrib/sign_version index f3bd40f1..98a57374 100755 --- a/contrib/sign_version +++ b/contrib/sign_version @@ -1,4 +1,4 @@ #!/bin/bash -version=`git describe --tags --dirty --always` +version=`python3 -c "import electrum; print(electrum.version.ELECTRUM_VERSION)"` sig=`./run_electrum -w $SIGNING_WALLET signmessage $SIGNING_ADDRESS $version` -echo "{ \"version\":\"$version\", \"signatures\":{ \"$addr\":\"$sig\"}}" +echo "{ \"version\":\"$version\", \"signatures\":{ \"$SIGNING_ADDRESS\":\"$sig\"}}" From 7b8114f865f644c5611c3bb849c4f4fc6ce9e376 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 14 Feb 2019 20:54:55 +0100 Subject: [PATCH 74/78] synchronizer: allow server not finding txn sometimes User has wallet file with history that includes some txid; corresponding raw tx is not in the "transactions" dict in the file however. When the synchronizer starts up, it requests this "missing" txn from the server... but what if the server does not know about it? Maybe it was reorged and is not in the new best chain, and not even in mempool. This was not handled previously. fix #5122 --- electrum/network.py | 2 +- electrum/synchronizer.py | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/electrum/network.py b/electrum/network.py index 030c50c6..b9e7afb9 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -780,7 +780,7 @@ class Network(PrintError): try: return await func(self, *args, **kwargs) except aiorpcx.jsonrpc.CodeMessageError as e: - raise UntrustedServerReturnedError(original_exception=e) + raise UntrustedServerReturnedError(original_exception=e) from e return wrapper @best_effort_reliable diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py index cdde6d24..f327669f 100644 --- a/electrum/synchronizer.py +++ b/electrum/synchronizer.py @@ -32,6 +32,7 @@ from aiorpcx import TaskGroup, run_in_thread from .transaction import Transaction from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer from .bitcoin import address_to_scripthash, is_address +from .network import UntrustedServerReturnedError if TYPE_CHECKING: from .network import Network @@ -165,7 +166,7 @@ class Synchronizer(SynchronizerBase): # Remove request; this allows up_to_date to be True self.requested_histories.pop(addr) - async def _request_missing_txs(self, hist): + async def _request_missing_txs(self, hist, *, allow_server_not_finding_tx=False): # "hist" is a list of [tx_hash, tx_height] lists transaction_hashes = [] for tx_hash, tx_height in hist: @@ -179,10 +180,18 @@ class Synchronizer(SynchronizerBase): if not transaction_hashes: return async with TaskGroup() as group: for tx_hash in transaction_hashes: - await group.spawn(self._get_transaction, tx_hash) + await group.spawn(self._get_transaction(tx_hash, allow_server_not_finding_tx=allow_server_not_finding_tx)) - async def _get_transaction(self, tx_hash): - result = await self.network.get_transaction(tx_hash) + async def _get_transaction(self, tx_hash, *, allow_server_not_finding_tx=False): + try: + result = await self.network.get_transaction(tx_hash) + except UntrustedServerReturnedError as e: + # most likely, "No such mempool or blockchain transaction" + if allow_server_not_finding_tx: + self.requested_tx.pop(tx_hash) + return + else: + raise tx = Transaction(result) try: tx.deserialize() @@ -207,7 +216,7 @@ class Synchronizer(SynchronizerBase): # Old electrum servers returned ['*'] when all history for the address # was pruned. This no longer happens but may remain in old wallets. if history == ['*']: continue - await self._request_missing_txs(history) + await self._request_missing_txs(history, allow_server_not_finding_tx=True) # add addresses to bootstrap for addr in self.wallet.get_addresses(): await self._add_address(addr) From 5313591c28b942b118f572b83b97a786c398503a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 15 Feb 2019 17:22:24 +0100 Subject: [PATCH 75/78] synchronizer: disconnect from server if cannot deserialize txn --- electrum/synchronizer.py | 22 +++++++++++++--------- electrum/transaction.py | 8 ++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py index f327669f..27f03563 100644 --- a/electrum/synchronizer.py +++ b/electrum/synchronizer.py @@ -39,6 +39,9 @@ if TYPE_CHECKING: from .address_synchronizer import AddressSynchronizer +class SynchronizerFailure(Exception): pass + + def history_status(h): if not h: return None @@ -194,18 +197,19 @@ class Synchronizer(SynchronizerBase): raise tx = Transaction(result) try: - tx.deserialize() - except Exception: - self.print_msg("cannot deserialize transaction, skipping", tx_hash) - return + tx.deserialize() # see if raises + except Exception as e: + # possible scenarios: + # 1: server is sending garbage + # 2: there is a bug in the deserialization code + # 3: there was a segwit-like upgrade that changed the tx structure + # that we don't know about + raise SynchronizerFailure(f"cannot deserialize transaction {tx_hash}") from e if tx_hash != tx.txid(): - self.print_error("received tx does not match expected txid ({} != {})" - .format(tx_hash, tx.txid())) - return + raise SynchronizerFailure(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})") tx_height = self.requested_tx.pop(tx_hash) self.wallet.receive_tx_callback(tx_hash, tx, tx_height) - self.print_error("received tx %s height: %d bytes: %d" % - (tx_hash, tx_height, len(tx.raw))) + self.print_error(f"received tx {tx_hash} height: {tx_height} bytes: {len(tx.raw)}") # callbacks self.wallet.network.trigger_callback('new_transaction', self.wallet, tx) diff --git a/electrum/transaction.py b/electrum/transaction.py index 32d2e4cc..d29aba4c 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -125,7 +125,7 @@ class BCDataStream(object): self.read_cursor += length return result except IndexError: - raise SerializationError("attempt to read past end of buffer") + raise SerializationError("attempt to read past end of buffer") from None def can_read_more(self) -> bool: if not self.input: @@ -159,8 +159,8 @@ class BCDataStream(object): elif size == 255: size = self._read_num(' Date: Fri, 15 Feb 2019 10:47:21 +0100 Subject: [PATCH 76/78] improve network send_multiple_requests --- electrum/network.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/electrum/network.py b/electrum/network.py index b9e7afb9..3a290536 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -1188,24 +1188,21 @@ class Network(PrintError): return parse_servers(await session.send_request('server.peers.subscribe')) async def send_multiple_requests(self, servers: List[str], method: str, params: Sequence): - num_connecting = len(self.connecting) - for server in servers: - self._start_interface(server) - # sleep a bit - for _ in range(10): - if len(self.connecting) < num_connecting: - break - await asyncio.sleep(1) responses = dict() - async def get_response(iface: Interface): + async def get_response(server): + interface = Interface(self, server, self.proxy) + timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent) try: - res = await iface.session.send_request(method, params, timeout=10) + await asyncio.wait_for(interface.ready, timeout) + except BaseException as e: + await interface.close() + return + try: + res = await interface.session.send_request(method, params, timeout=10) except Exception as e: res = e - responses[iface.server] = res + responses[interface.server] = res async with TaskGroup() as group: for server in servers: - interface = self.interfaces.get(server) - if interface: - await group.spawn(get_response(interface)) + await group.spawn(get_response(server)) return responses From 92bf409bf0c088001c049639513ea32384555909 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 15 Feb 2019 21:14:08 +0100 Subject: [PATCH 77/78] scripts: add "quick_start" to showcase some basic functionality --- electrum/scripts/quick_start.py | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 electrum/scripts/quick_start.py diff --git a/electrum/scripts/quick_start.py b/electrum/scripts/quick_start.py new file mode 100644 index 00000000..27d821ad --- /dev/null +++ b/electrum/scripts/quick_start.py @@ -0,0 +1,40 @@ +import os + +from electrum.simple_config import SimpleConfig +from electrum import constants +from electrum.daemon import Daemon +from electrum.storage import WalletStorage +from electrum.wallet import Wallet +from electrum.commands import Commands + + +config = SimpleConfig({"testnet": True}) # to use ~/.electrum/testnet as datadir +constants.set_testnet() # to set testnet magic bytes +daemon = Daemon(config, listen_jsonrpc=False) +network = daemon.network +assert network.asyncio_loop.is_running() + +command_runner = Commands(config, wallet=None, network=network) + +# get wallet on disk +wallet_dir = os.path.dirname(config.get_wallet_path()) +wallet_path = os.path.join(wallet_dir, "test_wallet") +if not os.path.exists(wallet_path): + config.set_key('wallet_path', wallet_path) + command_runner.create(segwit=True) + +# open wallet +storage = WalletStorage(wallet_path) +wallet = Wallet(storage) +wallet.start_network(network) + +# you can use ~CLI commands by accessing command_runner +command_runner.wallet = wallet +print("balance", command_runner.getbalance()) +print("addr", command_runner.getunusedaddress()) +print("gettx", command_runner.gettransaction("bd3a700b2822e10a034d110c11a596ee7481732533eb6aca7f9ca02911c70a4f")) + +# but you might as well interact with the underlying methods directly +print("balance", wallet.get_balance()) +print("addr", wallet.get_unused_address()) +print("gettx", network.run_from_another_thread(network.get_transaction("bd3a700b2822e10a034d110c11a596ee7481732533eb6aca7f9ca02911c70a4f"))) From 0de954546ac82fc4b2c474567493b5afb9698a19 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 15 Feb 2019 22:06:10 +0100 Subject: [PATCH 78/78] test python version in main script for better error text on old python related: #5008, #5129 --- run_electrum | 9 +++++++++ setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/run_electrum b/run_electrum index 68040693..4f5285db 100755 --- a/run_electrum +++ b/run_electrum @@ -26,6 +26,15 @@ import os import sys + +MIN_PYTHON_VERSION = "3.6.1" # FIXME duplicated from setup.py +_min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split(".")))) + + +if sys.version_info[:3] < _min_python_version_tuple: + sys.exit("Error: Electrum requires Python version >= %s..." % MIN_PYTHON_VERSION) + + script_dir = os.path.dirname(os.path.realpath(__file__)) is_bundle = getattr(sys, 'frozen', False) is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop")) diff --git a/setup.py b/setup.py index 947e01ce..40966474 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ _min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split(".")))) if sys.version_info[:3] < _min_python_version_tuple: - sys.exit("Error: Electrum requires Python version >= {}...".format(MIN_PYTHON_VERSION)) + sys.exit("Error: Electrum requires Python version >= %s..." % MIN_PYTHON_VERSION) with open('contrib/requirements/requirements.txt') as f: requirements = f.read().splitlines()