Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
gruve-p committed Nov 28, 2023
2 parents dcebe30 + 6467db0 commit 4784b6e
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 29 deletions.
Binary file added electrum_grs/gui/icons/hd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added electrum_grs/gui/icons/hd_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added electrum_grs/gui/icons/script.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added electrum_grs/gui/icons/script_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions electrum_grs/gui/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ def to_rtf(msg):
"""This channel is with a non-trampoline node; it cannot be used if trampoline is enabled.
If you want to keep using this channel, you need to disable trampoline routing in your preferences."""
)

MSG_FREEZE_ADDRESS = _("When you freeze an address, the funds in that address will not be used for sending bitcoins.")
MSG_FREEZE_COIN = _("When you freeze a coin, it will not be used for sending bitcoins.")
2 changes: 2 additions & 0 deletions electrum_grs/gui/qml/components/WalletDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,15 @@ Pane {
text: Daemon.currentWallet.txinType
font.pixelSize: constants.fontSizeSmall
font.bold: true
iconSource: '../../../icons/script_white.png'
}
Tag {
Layout.alignment: Qt.AlignHCenter
text: qsTr('HD')
visible: Daemon.currentWallet.isDeterministic
font.pixelSize: constants.fontSizeSmall
font.bold: true
iconSource: '../../../icons/hd_white.png'
}
Tag {
Layout.alignment: Qt.AlignHCenter
Expand Down
2 changes: 1 addition & 1 deletion electrum_grs/gui/qml/components/wizard/WCImport.qml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ WizardComponent {
dialog.onFound.connect(function() {
if (verify(dialog.scanData)) {
if (import_ta.text != '')
import_ta.text = import_ta.text + ',\n'
import_ta.text = import_ta.text + '\n'
import_ta.text = import_ta.text + dialog.scanData
}
dialog.close()
Expand Down
13 changes: 9 additions & 4 deletions electrum_grs/gui/qt/address_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

from .util import MONOSPACE_FONT, ColorScheme, webopen
from .my_treeview import MyTreeView, MySortModel
from ..messages import MSG_FREEZE_ADDRESS

if TYPE_CHECKING:
from .main_window import ElectrumWindow
Expand Down Expand Up @@ -284,6 +285,7 @@ def create_menu(self, position):
multi_select = len(selected) > 1
addrs = [self.item_from_index(item).text() for item in selected]
menu = QMenu()
menu.setToolTipsVisible(True)
if not multi_select:
idx = self.indexAt(position)
if not idx.isValid():
Expand Down Expand Up @@ -311,14 +313,17 @@ def create_menu(self, position):
menu.addAction(_("View on block explorer"), lambda: webopen(addr_URL))

if not self.wallet.is_frozen_address(addr):
menu.addAction(_("Freeze"), lambda: self.main_window.set_frozen_state_of_addresses([addr], True))
act = menu.addAction(_("Freeze"), lambda: self.main_window.set_frozen_state_of_addresses([addr], True))
else:
menu.addAction(_("Unfreeze"), lambda: self.main_window.set_frozen_state_of_addresses([addr], False))
act = menu.addAction(_("Unfreeze"), lambda: self.main_window.set_frozen_state_of_addresses([addr], False))
act.setToolTip(MSG_FREEZE_ADDRESS)

else:
# multiple items selected
menu.addAction(_("Freeze"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, True))
menu.addAction(_("Unfreeze"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False))
act = menu.addAction(_("Freeze"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, True))
act.setToolTip(MSG_FREEZE_ADDRESS)
act = menu.addAction(_("Unfreeze"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False))
act.setToolTip(MSG_FREEZE_ADDRESS)

coins = self.wallet.get_spendable_coins(addrs)
if coins:
Expand Down
18 changes: 12 additions & 6 deletions electrum_grs/gui/qt/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,18 @@ def write(self, text):
exec(command, self.namespace, self.namespace)
except SystemExit:
self.close()
except BaseException:
traceback_lines = traceback.format_exc().split('\n')
# Remove traceback mentioning this file, and a linebreak
for i in (3,2,1,-1):
traceback_lines.pop(i)
self.appendPlainText('\n'.join(traceback_lines))
except BaseException as e:
te = traceback.TracebackException.from_exception(e)
# rm part of traceback mentioning this file.
# (note: we rm stack items before converting to str, instead of removing lines from the str,
# as this is more reliable. The latter would differ whether the traceback has source text lines,
# which is not always the case.)
te.stack = traceback.StackSummary.from_list(te.stack[1:])
tb_str = "".join(te.format())
# rm last linebreak:
if tb_str.endswith("\n"):
tb_str = tb_str[:-1]
self.appendPlainText(tb_str)
sys.stdout = tmp_stdout

def keyPressEvent(self, event):
Expand Down
10 changes: 9 additions & 1 deletion electrum_grs/gui/qt/network_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox,
QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox,
QTabWidget, QWidget, QLabel)
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtGui import QIntValidator

from electrum_grs.i18n import _
from electrum_grs import constants, blockchain, util
Expand All @@ -53,6 +53,7 @@
protocol_names = ['TCP', 'SSL']
protocol_letters = 'ts'


class NetworkDialog(QDialog, QtEventListener):
def __init__(self, *, network: Network, config: 'SimpleConfig'):
QDialog.__init__(self)
Expand Down Expand Up @@ -243,6 +244,9 @@ def __init__(self, network: Network, config: 'SimpleConfig', wizard=False):
self.proxy_host.setFixedWidth(fixed_width_hostname)
self.proxy_port = QLineEdit()
self.proxy_port.setFixedWidth(fixed_width_port)
self.proxy_port_validator = QIntValidator(1, 65535)
self.proxy_port.setValidator(self.proxy_port_validator)

self.proxy_user = QLineEdit()
self.proxy_user.setPlaceholderText(_("Proxy user"))
self.proxy_password = PasswordLineEdit()
Expand Down Expand Up @@ -427,6 +431,10 @@ def set_server(self):
def set_proxy(self):
net_params = self.network.get_parameters()
if self.proxy_cb.isChecked():
if not self.proxy_port.hasAcceptableInput():
return
if ':' in self.proxy_host.text(): # avoid deserialization pitfall
return
proxy = {'mode':str(self.proxy_mode.currentText()).lower(),
'host':str(self.proxy_host.text()),
'port':str(self.proxy_port.text()),
Expand Down
25 changes: 17 additions & 8 deletions electrum_grs/gui/qt/utxo_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from .util import ColorScheme, MONOSPACE_FONT, EnterButton
from .my_treeview import MyTreeView
from .new_channel_dialog import NewChannelDialog
from ..messages import MSG_FREEZE_ADDRESS, MSG_FREEZE_COIN

if TYPE_CHECKING:
from .main_window import ElectrumWindow
Expand Down Expand Up @@ -317,28 +318,36 @@ def create_menu(self, position):
utxo = coins[0]
addr = utxo.address
menu_freeze = menu.addMenu(_("Freeze"))
menu_freeze.setToolTipsVisible(True)
if not self.wallet.is_frozen_coin(utxo):
menu_freeze.addAction(_("Freeze Coin"), lambda: self.main_window.set_frozen_state_of_coins([utxo], True))
act = menu_freeze.addAction(_("Freeze Coin"), lambda: self.main_window.set_frozen_state_of_coins([utxo], True))
else:
menu_freeze.addAction(_("Unfreeze Coin"), lambda: self.main_window.set_frozen_state_of_coins([utxo], False))
act = menu_freeze.addAction(_("Unfreeze Coin"), lambda: self.main_window.set_frozen_state_of_coins([utxo], False))
act.setToolTip(MSG_FREEZE_COIN)
if not self.wallet.is_frozen_address(addr):
menu_freeze.addAction(_("Freeze Address"), lambda: self.main_window.set_frozen_state_of_addresses([addr], True))
act = menu_freeze.addAction(_("Freeze Address"), lambda: self.main_window.set_frozen_state_of_addresses([addr], True))
else:
menu_freeze.addAction(_("Unfreeze Address"), lambda: self.main_window.set_frozen_state_of_addresses([addr], False))
act = menu_freeze.addAction(_("Unfreeze Address"), lambda: self.main_window.set_frozen_state_of_addresses([addr], False))
act.setToolTip(MSG_FREEZE_ADDRESS)
elif len(coins) > 1: # multiple items selected
menu.addSeparator()
addrs = [utxo.address for utxo in coins]
is_coin_frozen = [self.wallet.is_frozen_coin(utxo) for utxo in coins]
is_addr_frozen = [self.wallet.is_frozen_address(utxo.address) for utxo in coins]
menu_freeze = menu.addMenu(_("Freeze"))
menu_freeze.setToolTipsVisible(True)
if not all(is_coin_frozen):
menu_freeze.addAction(_("Freeze Coins"), lambda: self.main_window.set_frozen_state_of_coins(coins, True))
act = menu_freeze.addAction(_("Freeze Coins"), lambda: self.main_window.set_frozen_state_of_coins(coins, True))
act.setToolTip(MSG_FREEZE_COIN)
if any(is_coin_frozen):
menu_freeze.addAction(_("Unfreeze Coins"), lambda: self.main_window.set_frozen_state_of_coins(coins, False))
act = menu_freeze.addAction(_("Unfreeze Coins"), lambda: self.main_window.set_frozen_state_of_coins(coins, False))
act.setToolTip(MSG_FREEZE_COIN)
if not all(is_addr_frozen):
menu_freeze.addAction(_("Freeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, True))
act = menu_freeze.addAction(_("Freeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, True))
act.setToolTip(MSG_FREEZE_ADDRESS)
if any(is_addr_frozen):
menu_freeze.addAction(_("Unfreeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False))
act = menu_freeze.addAction(_("Unfreeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False))
act.setToolTip(MSG_FREEZE_ADDRESS)

menu.exec_(self.viewport().mapToGlobal(position))

Expand Down
12 changes: 6 additions & 6 deletions electrum_grs/json_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def __init__(self, s: str, storage=None, encoder=None, upgrader=None):
self.data = StoredDict(data, self, [])
# write file in case there was a db upgrade
if self.storage and self.storage.file_exists():
self._write()
self.write_and_force_consolidation()

def load_data(self, s:str) -> dict:
""" overloaded in wallet_db """
Expand Down Expand Up @@ -381,10 +381,10 @@ def _convert_value(self, path, key, v):

@locked
def write(self):
if not self.storage.file_exists()\
or self.storage.is_encrypted()\
or self.storage.needs_consolidation():
self._write()
if (not self.storage.file_exists()
or self.storage.is_encrypted()
or self.storage.needs_consolidation()):
self.write_and_force_consolidation()
else:
self._append_pending_changes()

Expand All @@ -402,7 +402,7 @@ def _append_pending_changes(self):

@locked
@profiler
def _write(self):
def write_and_force_consolidation(self):
if threading.current_thread().daemon:
raise Exception('daemon thread cannot write db')
if not self.modified():
Expand Down
5 changes: 4 additions & 1 deletion electrum_grs/keystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from unicodedata import normalize
import hashlib
import re
import copy
from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List, NamedTuple
from functools import lru_cache, wraps
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -1069,7 +1070,9 @@ def hardware_keystore(d) -> Hardware_KeyStore:
f'hw_keystores: {list(hw_keystores)}')

def load_keystore(db: 'WalletDB', name: str) -> KeyStore:
d = db.get(name, {})
# deepcopy object to avoid keeping a pointer to db.data
# note: this is needed as type(wallet.db.get("keystore")) != StoredDict
d = copy.deepcopy(db.get(name, {}))
t = d.get('type')
if not t:
raise WalletFileException(
Expand Down
34 changes: 34 additions & 0 deletions electrum_grs/tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,40 @@ def test_write_dictionary_to_file(self):
for key, value in some_dict.items():
self.assertEqual(d[key], value)

async def test_storage_imported_add_privkeys_persistence_test(self):
text = ' '.join([
'p2wpkh:L4jkdiXszG26SUYvwwJhzGwg37H2nLhrbip7u6crmgNeJysv5FHL',
'p2wpkh:L24GxnN7NNUAfCXA6hFzB1jt59fYAAiFZMcLaJ2ZSawGpM3uqhb1'
])
d = restore_wallet_from_text(text, path=self.wallet_path, config=self.config)
wallet = d['wallet'] # type: Imported_Wallet
self.assertEqual(2, len(wallet.get_receiving_addresses()))

wallet.save_db()

# open the wallet anew again, and add a privkey. This should add the new data as a json_patch
wallet = None
storage = WalletStorage(self.wallet_path)
db = WalletDB(storage.read(), storage=storage, upgrade=True)
wallet = Wallet(db, config=self.config)

wallet.import_private_keys(['p2wpkh:KzuqaaLp9zYjVuj8vQtCwFdiZFreW3NJNBachgVS8S9XMgj5y78b'], password=None)
self.assertEqual(3, len(wallet.get_receiving_addresses()))
self.assertEqual(3, len(wallet.keystore.keypairs))
wallet.save_db()

# open the wallet anew again, and verify if the privkey was stored
wallet = None
storage = WalletStorage(self.wallet_path)
db = WalletDB(storage.read(), storage=storage, upgrade=True)
wallet = Wallet(db, config=self.config)
self.assertEqual(3, len(wallet.get_receiving_addresses()))
self.assertEqual(3, len(wallet.keystore.keypairs))
self.assertTrue('03bf450797034dc95693096e575e3b3db14e5f074679b349b727f90fc7804ce7ab' in wallet.keystore.keypairs)
self.assertTrue('030dac677b9484e23db6f9255eddf433f4f12c02f9b35e0100f2f103ffbccf540f' in wallet.keystore.keypairs)
self.assertTrue('02f11d5f222a728fd08226cb5a1e85a74d58fc257bd3764bf1234346f91defed72' in wallet.keystore.keypairs)


class FakeExchange(ExchangeBase):
def __init__(self, rate):
super().__init__(lambda self: None, lambda self: None)
Expand Down
4 changes: 2 additions & 2 deletions electrum_grs/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2864,9 +2864,9 @@ def update_password(self, old_pw, new_pw, *, encrypt_storage: bool = True):
self._update_password_for_keystore(old_pw, new_pw)
encrypt_keystore = self.can_have_keystore_encryption()
self.db.set_keystore_encryption(bool(new_pw) and encrypt_keystore)
## save changes
# save changes. force full rewrite to rm remnants of old password
if self.storage and self.storage.file_exists():
self.db._write()
self.db.write_and_force_consolidation()
# if wallet was previously unlocked, update password in memory
if self._password_in_memory is not None:
self._password_in_memory = new_pw
Expand Down

0 comments on commit 4784b6e

Please sign in to comment.