electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 35ae2a0064b5548b5c337280a2763f3534ca569f
parent 5fef1e7980e6c9811448ad7d9fb6afa4460ac7fc
Author: ThomasV <thomasv@electrum.org>
Date:   Mon, 19 Mar 2018 11:57:24 +0100

Merge pull request #4143 from SomberNight/wallet_file_exception

Catch wallet file related exceptions in Qt wizard
Diffstat:
Mgui/qt/__init__.py | 16++++++++++++----
Mlib/bitcoin.py | 13++++++++-----
Mlib/keystore.py | 20++++++++++++--------
Mlib/storage.py | 26++++++++++++++------------
Mlib/util.py | 6++++++
Mlib/wallet.py | 18++++++++++--------
Mplugins/trustedcoin/trustedcoin.py | 2+-
7 files changed, 63 insertions(+), 38 deletions(-)

diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py @@ -44,7 +44,8 @@ from electrum import WalletStorage # from electrum.synchronizer import Synchronizer # from electrum.verifier import SPV # from electrum.util import DebugMem -from electrum.util import UserCancelled, print_error +from electrum.util import (UserCancelled, print_error, + WalletFileException, BitcoinException) # from electrum.wallet import Abstract_Wallet from .installwizard import InstallWizard, GoBack @@ -191,7 +192,7 @@ class ElectrumGui: except BaseException as e: traceback.print_exc(file=sys.stdout) d = QMessageBox(QMessageBox.Warning, _('Error'), - _('Cannot load wallet:') + '\n' + str(e)) + _('Cannot load wallet') + ' (1):\n' + str(e)) d.exec_() return if not wallet: @@ -203,7 +204,14 @@ class ElectrumGui: pass except GoBack as e: print_error('[start_new_window] Exception caught (GoBack)', e) - wizard.terminate() + except (WalletFileException, BitcoinException) as e: + traceback.print_exc(file=sys.stderr) + d = QMessageBox(QMessageBox.Warning, _('Error'), + _('Cannot load wallet') + ' (2):\n' + str(e)) + d.exec_() + return + finally: + wizard.terminate() if not wallet: return @@ -220,7 +228,7 @@ class ElectrumGui: except BaseException as e: traceback.print_exc(file=sys.stdout) d = QMessageBox(QMessageBox.Warning, _('Error'), - _('Cannot create window for wallet:') + '\n' + str(e)) + _('Cannot create window for wallet') + ':\n' + str(e)) d.exec_() return if uri: diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -32,7 +32,7 @@ import json import ecdsa import pyaes -from .util import bfh, bh2u, to_string +from .util import bfh, bh2u, to_string, BitcoinException from . import version from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict from . import segwit_addr @@ -349,7 +349,7 @@ def address_to_script(addr): script += push_script(bh2u(hash_160)) script += '87' # op_equal else: - raise BaseException('unknown address type') + raise BitcoinException('unknown address type: {}'.format(addrtype)) return script def address_to_scripthash(addr): @@ -493,7 +493,8 @@ def deserialize_privkey(key): vch = DecodeBase58Check(key) except BaseException: neutered_privkey = str(key)[:3] + '..' + str(key)[-2:] - raise BaseException("cannot deserialize", neutered_privkey) + raise BitcoinException("cannot deserialize privkey {}" + .format(neutered_privkey)) if txin_type is None: # keys exported in version 3.0.x encoded script type in first byte @@ -888,7 +889,8 @@ def deserialize_xkey(xkey, prv, *, net=None): net = constants.net xkey = DecodeBase58Check(xkey) if len(xkey) != 78: - raise BaseException('Invalid length') + raise BitcoinException('Invalid length for extended key: {}' + .format(len(xkey))) depth = xkey[4] fingerprint = xkey[5:9] child_number = xkey[9:13] @@ -896,7 +898,8 @@ def deserialize_xkey(xkey, prv, *, net=None): header = int('0x' + bh2u(xkey[0:4]), 16) headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS if header not in headers.values(): - raise BaseException('Invalid xpub format', hex(header)) + raise BitcoinException('Invalid extended key format: {}' + .format(hex(header))) xtype = list(headers.keys())[list(headers.values()).index(header)] n = 33 if prv else 32 K_or_k = xkey[13+n:] diff --git a/lib/keystore.py b/lib/keystore.py @@ -29,7 +29,8 @@ from unicodedata import normalize from . import bitcoin from .bitcoin import * from . import constants -from .util import PrintError, InvalidPassword, hfu +from .util import (PrintError, InvalidPassword, hfu, WalletFileException, + BitcoinException) from .mnemonic import Mnemonic, load_wordlist from .plugins import run_hook @@ -615,7 +616,8 @@ def xpubkey_to_address(x_pubkey): mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey) pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1]) else: - raise BaseException("Cannot parse pubkey") + raise BitcoinException("Cannot parse pubkey. prefix: {}" + .format(x_pubkey[0:2])) if pubkey: address = public_key_to_p2pkh(bfh(pubkey)) return pubkey, address @@ -634,14 +636,15 @@ def hardware_keystore(d): if hw_type in hw_keystores: constructor = hw_keystores[hw_type] return constructor(d) - raise BaseException('unknown hardware type', hw_type) + raise WalletFileException('unknown hardware type: {}'.format(hw_type)) def load_keystore(storage, name): - w = storage.get('wallet_type', 'standard') d = storage.get(name, {}) t = d.get('type') if not t: - raise BaseException('wallet format requires update') + raise WalletFileException( + 'Wallet format requires update.\n' + 'Cannot find keystore for name {}'.format(name)) if t == 'old': k = Old_KeyStore(d) elif t == 'imported': @@ -651,7 +654,8 @@ def load_keystore(storage, name): elif t == 'hardware': k = hardware_keystore(d) else: - raise BaseException('unknown wallet type', t) + raise WalletFileException( + 'Unknown type {} for keystore named {}'.format(t, name)) return k @@ -709,7 +713,7 @@ def from_seed(seed, passphrase, is_p2sh): xtype = 'p2wsh' if is_p2sh else 'p2wpkh' keystore.add_xprv_from_seed(bip32_seed, xtype, der) else: - raise BaseException(t) + raise BitcoinException('Unexpected seed type {}'.format(t)) return keystore def from_private_key_list(text): @@ -743,5 +747,5 @@ def from_master_key(text): elif is_xpub(text): k = from_xpub(text) else: - raise BaseException('Invalid key') + raise BitcoinException('Invalid master key') return k diff --git a/lib/storage.py b/lib/storage.py @@ -33,7 +33,7 @@ import pbkdf2, hmac, hashlib import base64 import zlib -from .util import PrintError, profiler, InvalidPassword +from .util import PrintError, profiler, InvalidPassword, WalletFileException from .plugins import run_hook, plugin_loaders from .keystore import bip44_derivation from . import bitcoin @@ -113,7 +113,7 @@ class WalletStorage(PrintError): if not self.manual_upgrades: if self.requires_split(): - raise BaseException("This wallet has multiple accounts and must be split") + raise WalletFileException("This wallet has multiple accounts and must be split") if self.requires_upgrade(): self.upgrade() @@ -174,7 +174,7 @@ class WalletStorage(PrintError): elif v == STO_EV_XPUB_PW: return b'BIE2' else: - raise Exception('no encryption magic for version: %s' % v) + raise WalletFileException('no encryption magic for version: %s' % v) def decrypt(self, password): ec_key = self.get_key(password) @@ -320,7 +320,7 @@ class WalletStorage(PrintError): storage2.write() result.append(new_path) else: - raise BaseException("This wallet has multiple accounts and must be split") + raise WalletFileException("This wallet has multiple accounts and must be split") return result def requires_upgrade(self): @@ -419,7 +419,7 @@ class WalletStorage(PrintError): d['seed'] = seed self.put(key, d) else: - raise Exception('Unable to tell wallet type. Is this even a wallet file?') + raise WalletFileException('Unable to tell wallet type. Is this even a wallet file?') # remove junk self.put('master_public_key', None) self.put('master_public_keys', None) @@ -543,7 +543,7 @@ class WalletStorage(PrintError): else: addresses.append(addr) if addresses and keypairs: - raise BaseException('mixed addresses and privkeys') + raise WalletFileException('mixed addresses and privkeys') elif addresses: self.put('addresses', addresses) self.put('accounts', None) @@ -553,7 +553,7 @@ class WalletStorage(PrintError): self.put('keypairs', keypairs) self.put('accounts', None) else: - raise BaseException('no addresses or privkeys') + raise WalletFileException('no addresses or privkeys') def convert_account(self): if not self._is_upgrade_method_needed(0, 13): @@ -566,9 +566,9 @@ class WalletStorage(PrintError): if cur_version > max_version: return False elif cur_version < min_version: - raise BaseException( - ('storage upgrade: unexpected version %d (should be %d-%d)' - % (cur_version, min_version, max_version))) + raise WalletFileException( + 'storage upgrade: unexpected version {} (should be {}-{})' + .format(cur_version, min_version, max_version)) else: return True @@ -584,7 +584,9 @@ class WalletStorage(PrintError): if not seed_version: seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_SEED_VERSION if seed_version > FINAL_SEED_VERSION: - raise BaseException('This version of Electrum is too old to open this wallet') + raise WalletFileException('This version of Electrum is too old to open this wallet.\n' + '(highest supported storage version: {}, version of this file: {})' + .format(FINAL_SEED_VERSION, seed_version)) if seed_version==14 and self.get('seed_type') == 'segwit': self.raise_unsupported_version(seed_version) if seed_version >=12: @@ -607,4 +609,4 @@ class WalletStorage(PrintError): else: # creation was complete if electrum was run from source msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet." - raise BaseException(msg) + raise WalletFileException(msg) diff --git a/lib/util.py b/lib/util.py @@ -84,6 +84,12 @@ class TimeoutException(Exception): return self.message +class WalletFileException(Exception): pass + + +class BitcoinException(Exception): pass + + # Throw this exception to unwind the stack like when an error occurs. # However unlike other exceptions the user won't be informed. class UserCancelled(Exception): diff --git a/lib/wallet.py b/lib/wallet.py @@ -44,7 +44,8 @@ import sys from .i18n import _ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, - format_satoshis, NoDynamicFeeEstimates, TimeoutException) + format_satoshis, NoDynamicFeeEstimates, TimeoutException, + WalletFileException, BitcoinException) from .bitcoin import * from .version import * @@ -131,6 +132,7 @@ def sweep_preparations(privkeys, network, imax=100): find_utxos_for_privkey('p2pk', privkey, compressed) if not inputs: raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)')) + # FIXME actually inputs need not be confirmed now, see https://github.com/kyuupichan/electrumx/issues/365 return inputs, keypairs @@ -335,7 +337,7 @@ class Abstract_Wallet(PrintError): addrs = self.get_receiving_addresses() if len(addrs) > 0: if not bitcoin.is_address(addrs[0]): - raise Exception('The addresses in this wallet are not bitcoin addresses.') + raise WalletFileException('The addresses in this wallet are not bitcoin addresses.') def synchronize(self): pass @@ -1166,7 +1168,7 @@ class Abstract_Wallet(PrintError): _type, data, value = o if _type == TYPE_ADDRESS: if not is_address(data): - raise BaseException("Invalid bitcoin address:" + data) + raise BaseException("Invalid bitcoin address: {}".format(data)) if value == '!': if i_max is not None: raise BaseException("More than one output set to spend max") @@ -1339,7 +1341,7 @@ class Abstract_Wallet(PrintError): def bump_fee(self, tx, delta): if tx.is_final(): - raise BaseException(_("Cannot bump fee: transaction is final")) + raise BaseException(_('Cannot bump fee') + ': ' + _('transaction is final')) inputs = copy.deepcopy(tx.inputs()) outputs = copy.deepcopy(tx.outputs()) for txin in inputs: @@ -1370,7 +1372,7 @@ class Abstract_Wallet(PrintError): if delta > 0: continue if delta > 0: - raise BaseException(_('Cannot bump fee: could not find suitable outputs')) + raise BaseException(_('Cannot bump fee') + ': ' + _('could not find suitable outputs')) locktime = self.get_local_height() tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) tx_new.BIP_LI01_sort() @@ -1944,14 +1946,14 @@ class Imported_Wallet(Simple_Wallet): txin_type, pubkey = self.keystore.import_privkey(sec, pw) except Exception: neutered_privkey = str(sec)[:3] + '..' + str(sec)[-2:] - raise BaseException('Invalid private key', neutered_privkey) + raise BitcoinException('Invalid private key: {}'.format(neutered_privkey)) if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']: if redeem_script is not None: - raise BaseException('Cannot use redeem script with', txin_type) + raise BitcoinException('Cannot use redeem script with script type {}'.format(txin_type)) addr = bitcoin.pubkey_to_address(txin_type, pubkey) elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: if redeem_script is None: - raise BaseException('Redeem script required for', txin_type) + raise BitcoinException('Redeem script required for script type {}'.format(txin_type)) addr = bitcoin.redeem_script_to_address(txin_type, redeem_script) else: raise NotImplementedError(txin_type) diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py @@ -406,7 +406,7 @@ class TrustedCoinPlugin(BasePlugin): xprv1, xpub1 = self.get_xkeys(seed, passphrase, "m/0'/") xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/") else: - raise BaseException('unrecognized seed length') + raise BaseException('unrecognized seed length: {} words'.format(n)) return xprv1, xpub1, xprv2, xpub2 def create_keystore(self, wizard, seed, passphrase):