Skip to content

Commit

Permalink
Refactor/polish of the wallet wizard.
Browse files Browse the repository at this point in the history
  • Loading branch information
rt121212121 committed Mar 21, 2020
1 parent 784e9c9 commit 71a039d
Show file tree
Hide file tree
Showing 17 changed files with 1,025 additions and 701 deletions.
15 changes: 7 additions & 8 deletions electrumsv/daemon.py
Expand Up @@ -225,12 +225,12 @@ def run_daemon(self, config_options: dict) -> Any:
if sub in [None, 'start']:
response = "Daemon already running"
elif sub == 'load_wallet':
path = config.get_wallet_path()
wallet = self.load_wallet(path)
path = config.get_cmdline_wallet_filepath()
wallet = self.load_wallet(path) if path is not None else None
self.cmd_runner._wallet = wallet
response = True
elif sub == 'close_wallet':
path = WalletStorage.canonical_path(config.get_wallet_path())
path = WalletStorage.canonical_path(config.get_cmdline_wallet_filepath())
if path in self.wallets:
self.stop_wallet_at_path(path)
response = True
Expand All @@ -255,8 +255,7 @@ def run_daemon(self, config_options: dict) -> Any:
def run_gui(self, config_options: dict) -> str:
config = SimpleConfig(config_options)
if hasattr(app_state, 'windows'):
config.open_last_wallet()
path = config.get_wallet_path()
path = config.get_cmdline_wallet_filepath()
app_state.app.new_window(path, config.get('url'))
return "ok"

Expand Down Expand Up @@ -309,11 +308,11 @@ def run_cmdline(self, config_options: dict) -> Any:
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
if cmd.requires_wallet:
path = WalletStorage.canonical_path(config.get_wallet_path())
wallet = self.wallets.get(path)
wallet_path = WalletStorage.canonical_path(config.get_cmdline_wallet_filepath())
wallet = self.wallets.get(wallet_path)
if wallet is None:
return {'error': 'Wallet "%s" is not loaded. Use "electrum-sv daemon load_wallet"'
% get_wallet_name_from_path(path)}
% get_wallet_name_from_path(wallet_path)}
else:
wallet = None
# arguments passed to function
Expand Down
108 changes: 108 additions & 0 deletions electrumsv/data/text/wallet-wizard/choose-wallet.html
@@ -0,0 +1,108 @@
<h2>Choosing a wallet</h2>
<p>
This page allows you to either create a new wallet or select an existing wallet to load.
If you want to create a new wallet, click on the button provided for that purpose. Otherwise,
if you want to select an existing wallet there are a range of ways you can do this.

<ul>
<li>If the wallet you want to select is one of those that were most recently opened, you
can locate it in the list of recently opened wallets and either double-click on it
or click on the button provided to open a selected wallet.</li>
<li>If the wallet you want to select is not in the list of recently opened wallets, you
can click on the button provided to open another wallet.</li>
</ul>
</p>
<h3>Creating a new wallet</h3>
<p>
On choosing to create a new wallet, you will be first asked to select where it will be located
and what it will be called. After doing so, you will then be asked to provide a password for
the wallet &mdash; while in the past it was possible to have a wallet without a password, this
is no longer supported. Finally, the new and empty wallet will be created and this wizard will
close and the wallet user interface opened.
</p>
<p>
New wallets require their user to create an initial account before they can be used. For now,
a wallet can only have one account, but when the wallet user interface has been redesigned
to support multiple accounts this restriction will be removed.
</p>
<h3>Selecting an existing wallet</h3>
<p>
Whichever method you use to select an existing wallet, depending on what that wallet is,
there are several things that might happen. If the wallet storage is the latest one required
by this version of ElectrumSV, then it will be opened. If it is an older storage format, then
it required going through a migration process to update it, before it can open.
</p>
<h4>Wallet storage formats</h4>
<p>
As of ElectrumSV 1.3.0, the wallet storage has changed from a JSON file to a database file.
There are numerous reasons for this change that will not be documented here. Versions 1.2.5
and earlier use a JSON file. Versions 1.3.0 and above use a database file.
</p>
<p>
There are three different ways in which a wallet in the older JSON format might work:

<ol>
<li><b>Unpassworded:</b> The saved wallet file was just the direct text of the JSON format.
Anyone could open the file in a text editor and see not only the private keys, but
also any seed words for that wallet.</li>
<li><b>Password with key encryption:</b> The saved wallet file was the direct text of the
JSON format. However the most critical data like private keys and seed words was
encrypted with the password.</li>
<li><b>Password with file encryption:</b> The saved wallet file was completely encrypted
with the password. In addition to this, critical data was also encrypted according
in the same way as basic passworded wallets.
</li>
</ol>
</p>
<h4>Migration</h4>
<p>
The specific subject of wallet migration is not covered in this document, you can read about
that if you are on the wallet migration wizard step. However, what the wallet selection process
will ask you to do before proceeding to that step, depends on your wallet's format.
</p>
<p>
Unpassworded JSON wallets will require the user to provide a password, before migration is
attempted. The password will be used to encrypt keys in the database, that were not previously
encrypted in the original JSON wallet. Passworded wallets will require the user to provide the
JSON wallet's existing password.
</p>
<h3>Wallet encryption</h3>
<p>
The naive way the wallets were stored using JSON was problematic, but because it was naive
it allowed a simple encryption of the entire wallet file as part of the process of writing
all the wallet data to disk. As long as no-one really used their wallets much, and they didn't
create many transactions this was fine and it was workable.
</p>
<p>
With Bitcoin SV, a user may decide to store all their data on-chain. To continue to use the
JSON files, would mean that potentially gigabytes if not terabytes of data would need to be
loaded and decrypted every time the wallet was used. And it would all need to be encrypted and
saved every time the wallet was exited.
</p>
<p>
For now the new database wallet files use key encryption. This is where only critical private
key-related data like actual private keys or seed words, are encrypted in the database.
We did prototype full privacy, but it complicated things to the point where it was unlikely
we would ever make a release. So we stepped back and did what was feasible.
</p>
<h4>Privacy</h4>
<p>
One concern about there no longer a way for wallets to have full encryption of all data,
is that the balance, transactions and other data can be viewed if someone has access to
your computer. However, if someone has access to your computer this is of less concern
than their ability to run software that may extract your private keys when you access them
&mdash; something that can be done regardless of full on-disk encryption of a wallet!
</p>
<p>
If a user really wants full privacy, and full on-disk encryption of their wallet they have
two options.

<ul>
<li>They can work with us and write support for it and we can merge their implementation
in for all users.</li>
<li>They can decrypt their wallet database before they load it, and encrypt it after they
have finished with it. This could be done manually each time, within a script that
automates wallet loading or implicitly done with full encryption of the disk the wallet
is stored on.</li>
</ul>
</p>
65 changes: 65 additions & 0 deletions electrumsv/data/text/wallet-wizard/migrate-wallet.html
@@ -0,0 +1,65 @@
<h2>Wallet migration</h2>
<p>
There are two situations where a wallet needs to be migrated. Older JSON wallets are migrated
to the latest database format. And more recent database wallets are also migrated to the latest
format when necessary.
</p>
<p>
If your wallet migrated successfully, just click the finish button and this wizard will close
and your wallet will open.
</p>
<h3>Backups</h3>
<p>
All wallet migrations back up the wallet within the same directory the original wallet
file is located. The process can be illustrated as follows:
<ol>
<li>A user opens a wallet named <code>mywallet</code>.</li>
<li>The migration process will rename <code>mywallet</code> to
<code>mywallet.backup.1</code>. If that file name is already in use, then it will
increase the number at the end. It will try and use <code>mywallet.backup.2</code>,
and if that fails it will try <code>mywallet.backup.3</code> and so on.</li>
<li>The migration process will create the migrated wallet as
<code>mywallet.sqlite</code></li>
</ol>
</p>
<h3>Migration problems</h3>
<p>
For the most part, there will not be any problems encountered when migrating your wallet.
But some wallets will fail to migrate, and if that happens to you, you should be able to
use the information below to aid you in discovering why or what to do in this situation.
</p>
<h4>Why did it fail?</h4>
<p>
There is only one identified situation where wallet migration fails. This is where the wallet
data is incomplete, and the migration process cannot link it together and finds that data it
needs is missing.
</p>
<h4>What can I do?</h4>
<p>
Open the wallet using ElectrumSV 1.2.5 and wait for it to synchronize. Make sure it connected
to the network to do so, and that it is fully up-to-date. In order to maintain sane wallet
file names you may want to:
<ol>
<li>Delete the failed <code>.sqlite</code> migrated wallet.</li>
<li>Rename the <code>.backup.1</code> file to it's original name.</li>
</ol>
To understand the cause of the naming, it may help to read the backup section above.
</p>
<p>
Otherwise, please follow these steps:
<ol>
<li>Open a DOS window, shell or terminal depending on your operating system.</li>
<li>Run ElectrumSV from the command-line with the extra argument <code>-v=debug</code>. For
example:<br/><br/>
MacOS: <code>/Applications/ElectrumSV.app/Contents/MacOS/1.3.0 -v=debug</code><br/>
Windows: <code>ElectrumSV-1.3.0.exe -v=debug</code><br/>
This should output debugging information which will be useful for whomever attempts
to fix the problem.
</li>
<li>Find the original wallet file, or the backup the failed migration process created,
and open it. It should fail migration again.</li>
<li>Exit ElectrumSV, and then copy the output which should also include an error.</li>
<li><a href="https://github.com/electrumsv/electrumsv/issues">Submit an issue</a> providing
both that output and as much information as possible.</li>
</ol>
</p>
13 changes: 13 additions & 0 deletions electrumsv/data/text/wallet-wizard/release-notes.html
@@ -0,0 +1,13 @@
<p>
Changes included in this version of ElectrumSV are documented in a more readable
form within the following article: <a href="https://medium.com/@roger.taylor/electrumsv-1-3-0b3-multisig-51b97311caae">ElectrumSV 1.3.0</a>
</p>
<p>
The most prominent changes are also summarised below for convenience, if you wish to read more
about any given change, please read the linked article.
<ul>
<li>The wallet storage has changed from a JSON file to an Sqlite database.</li>
<li>A wallet can now have multiple accounts, although they are currently limited to having
only one account until the user interface is reworked to support more than that.</li>
</ul>
</p>
5 changes: 3 additions & 2 deletions electrumsv/devices/hw_wallet/qt.py
Expand Up @@ -38,7 +38,8 @@
from electrumsv.i18n import _

from electrumsv.gui.qt.main_window import ElectrumWindow
from electrumsv.gui.qt.password_dialog import PasswordDialog, PasswordAction, PasswordLineEdit
from electrumsv.gui.qt.password_dialog import (ChangePasswordDialog, PasswordAction,
PasswordLineEdit)
from electrumsv.gui.qt.util import (
WindowModalDialog, Buttons, OkButton, CancelButton, WWLabel, read_QIcon,
)
Expand Down Expand Up @@ -133,7 +134,7 @@ def passphrase_dialog(self, msg, confirm):
# If confirm is true, require the user to enter the passphrase twice
parent = self.top_level_window()
if confirm:
d = PasswordDialog(parent, None, msg, PasswordAction.PASSPHRASE)
d = ChangePasswordDialog(parent, msg=msg, kind=PasswordAction.PASSPHRASE)
confirmed, p, passphrase = d.run()
else:
d = WindowModalDialog(parent, _("Enter Passphrase"))
Expand Down
3 changes: 3 additions & 0 deletions electrumsv/exchange_rate.py
@@ -1,4 +1,5 @@
from decimal import Decimal
from concurrent.futures import CancelledError
import csv
import datetime
import decimal
Expand Down Expand Up @@ -55,6 +56,8 @@ async def update(self, ccy):
logger.debug(f'getting fx quotes for {ccy}')
self.quotes = await run_in_thread(self.get_rates, ccy)
logger.debug('received fx quotes')
except CancelledError:
pass
except Exception:
logger.exception(f'exception updating FX quotes')

Expand Down
46 changes: 29 additions & 17 deletions electrumsv/gui/qt/app.py
Expand Up @@ -24,6 +24,7 @@
'''ElectrumSV application.'''

import datetime
import os
from functools import partial
import signal
import sys
Expand All @@ -49,7 +50,7 @@
from .label_sync import LabelSync
from .log_window import SVLogWindow, SVLogHandler
from .network_dialog import NetworkDialog
from .util import ColorScheme, read_QIcon, get_default_language
from .util import ColorScheme, get_default_language, MessageBox, read_QIcon
from .wallet_wizard import WalletWizard


Expand Down Expand Up @@ -154,9 +155,7 @@ def close_window(self, window):
self.windows.remove(window)
self.window_closed_signal.emit(window)
self._build_tray_menu()
# save wallet path of last open window
if not self.windows:
app_state.config.save_last_wallet(window._wallet)
self._last_window_closed()

def setup_app(self):
Expand Down Expand Up @@ -200,11 +199,11 @@ def _tray_activated(self, reason):
for w in self.windows:
w.hide()

def new_window(self, path, uri=None):
def new_window(self, path: Optional[str], uri: Optional[str]=None) -> None:
# Use a signal as can be called from daemon thread
self.create_new_window_signal.emit(path, uri)

def show_network_dialog(self, parent):
def show_network_dialog(self, parent) -> None:
if not app_state.daemon.network:
parent.show_warning(_('You are using ElectrumSV in offline mode; restart '
'ElectrumSV if you want to get connected'), title=_('Offline'))
Expand All @@ -217,7 +216,7 @@ def show_network_dialog(self, parent):
self.net_dialog = NetworkDialog(app_state.daemon.network, app_state.config)
self.net_dialog.show()

def show_log_viewer(self):
def show_log_viewer(self) -> None:
if self.log_window is None:
self.log_window = SVLogWindow(None, self.log_handler)
self.log_window.show()
Expand Down Expand Up @@ -272,21 +271,35 @@ def get_wallet_window_by_id(self, wallet_id: int) -> Optional[ElectrumWindow]:
if child_wallet.get_id() == wallet_id:
return w

def start_new_window(self, path, uri, is_startup=False):
def start_new_window(self, wallet_path: Optional[str], uri: Optional[str]=None,
is_startup: bool=False) -> Optional[ElectrumWindow]:
'''Raises the window for the wallet if it is open. Otherwise
opens the wallet and creates a new window for it.'''
for w in self.windows:
if w._wallet.get_storage_path() == path:
if w._wallet.get_storage_path() == wallet_path:
w.bring_to_top()
break
else:
wizard_window = WalletWizard(path, is_startup=is_startup)
result = wizard_window.run()
if result != QDialog.Accepted:
# Clean up?
return

wallet_path = wizard_window.get_wallet_path()
wizard_window: Optional[WalletWizard] = None
if wallet_path is not None:
is_valid, was_aborted, wizard_window = WalletWizard.attempt_open(wallet_path)
if was_aborted:
return None
if not is_valid:
wallet_filename = os.path.basename(wallet_path)
MessageBox.show_error(
_("Unable to load file '{}'.").format(wallet_filename))
return None
else:
wizard_window = WalletWizard(is_startup=is_startup)
if wizard_window is not None:
result = wizard_window.run()
if result != QDialog.Accepted:
return None
wallet_path = wizard_window.get_wallet_path()
# We cannot rely on accept alone indicating success.
if wallet_path is None:
return None
wallet = app_state.daemon.load_wallet(wallet_path)
assert wallet is not None
w = self._create_window_for_wallet(wallet)
Expand Down Expand Up @@ -338,8 +351,7 @@ def event_loop_started(self) -> None:
self.timer.start()
signal.signal(signal.SIGINT, lambda *args: self.quit())
self.initial_dialogs()
app_state.config.open_last_wallet()
path = app_state.config.get_wallet_path()
path = app_state.config.get_cmdline_wallet_filepath()
if not self.start_new_window(path, app_state.config.get('url'), is_startup=True):
self.quit()

Expand Down

0 comments on commit 71a039d

Please sign in to comment.