diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 275f977c..0df8fc94 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -24,11 +24,21 @@ # SOFTWARE. import os +import sys +import traceback + from . import bitcoin from . import keystore from .keystore import bip44_derivation from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types +from .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption from .i18n import _ +from .util import UserCancelled, InvalidPassword + +# hardware device setup purpose +HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2) + +class ScriptTypeNotSupported(Exception): pass class BaseWizard(object): @@ -59,7 +69,7 @@ class BaseWizard(object): f = getattr(self, action) f(*args) else: - raise BaseException("unknown action", action) + raise Exception("unknown action", action) def can_go_back(self): return len(self.stack)>1 @@ -112,7 +122,7 @@ class BaseWizard(object): choices = [ ('choose_seed_type', _('Create a new seed')), ('restore_from_seed', _('I already have a seed')), - ('restore_from_key', _('Use public or private keys')), + ('restore_from_key', _('Use a master key')), ] if not self.is_kivy: choices.append(('choose_hw_device', _('Use a hardware device'))) @@ -131,20 +141,26 @@ class BaseWizard(object): v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x) title = _("Import Zcash Addresses") message = _("Enter a list of Zcash addresses (this will create a watching-only wallet), or a list of private keys.") - self.add_xpub_dialog(title=title, message=message, run_next=self.on_import, is_valid=v) + self.add_xpub_dialog(title=title, message=message, run_next=self.on_import, + is_valid=v, allow_multi=True) def on_import(self, text): + # create a temporary wallet and exploit that modifications + # will be reflected on self.storage if keystore.is_address_list(text): - self.wallet = Imported_Wallet(self.storage) + w = Imported_Wallet(self.storage) for x in text.split(): - self.wallet.import_address(x) + w.import_address(x) elif keystore.is_private_key_list(text): k = keystore.Imported_KeyStore({}) self.storage.put('keystore', k.dump()) - self.wallet = Imported_Wallet(self.storage) - for x in text.split(): - self.wallet.import_private_key(x, None) - self.terminate() + w = Imported_Wallet(self.storage) + for x in keystore.get_private_keys(text): + w.import_private_key(x, None) + self.keystores.append(w.keystore) + else: + return self.terminate() + return self.run('create_wallet') def restore_from_key(self): if self.wallet_type == 'standard': @@ -163,7 +179,7 @@ class BaseWizard(object): k = keystore.from_master_key(text) self.on_keystore(k) - def choose_hw_device(self): + def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET): title = _('Hardware Keystore') # check available plugins support = self.plugins.get_hardware_support() @@ -172,68 +188,121 @@ class BaseWizard(object): _('No hardware wallet support found on your system.'), _('Please install the relevant libraries (eg python-trezor for Trezor).'), ]) - self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device()) + self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose)) return # scan devices devices = [] devmgr = self.plugins.device_manager - for name, description, plugin in support: - try: - # FIXME: side-effect: unpaired_device_info sets client.handler - u = devmgr.unpaired_device_infos(None, plugin) - except: - devmgr.print_error("error", name) - continue - devices += list(map(lambda x: (name, x), u)) + try: + scanned_devices = devmgr.scan_devices() + except BaseException as e: + devmgr.print_error('error scanning devices: {}'.format(e)) + debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e) + else: + debug_msg = '' + for name, description, plugin in support: + try: + # FIXME: side-effect: unpaired_device_info sets client.handler + u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices) + except BaseException as e: + devmgr.print_error('error getting device infos for {}: {}'.format(name, e)) + debug_msg += ' {}:\n {}\n'.format(plugin.name, e) + continue + devices += list(map(lambda x: (name, x), u)) + if not debug_msg: + debug_msg = ' {}'.format(_('No exceptions encountered.')) if not devices: msg = ''.join([ _('No hardware device detected.') + '\n', _('To trigger a rescan, press \'Next\'.') + '\n\n', _('If your device is not detected on Windows, go to "Settings", "Devices", "Connected devices", and do "Remove device". Then, plug your device again.') + ' ', - _('On Linux, you might have to add a new permission to your udev rules.'), + _('On Linux, you might have to add a new permission to your udev rules.') + '\n\n', + _('Debug message') + '\n', + debug_msg ]) - self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device()) + self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose)) return # select device self.devices = devices choices = [] for name, info in devices: state = _("initialized") if info.initialized else _("wiped") - label = info.label or _("An unnamed %s")%name + label = info.label or _("An unnamed {}").format(name) descr = "%s [%s, %s]" % (label, name, state) choices.append(((name, info), descr)) msg = _('Select a device') + ':' - self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_device) + self.choice_dialog(title=title, message=msg, choices=choices, run_next= lambda *args: self.on_device(*args, purpose=purpose)) - def on_device(self, name, device_info): + def on_device(self, name, device_info, *, purpose): self.plugin = self.plugins.get_plugin(name) try: - self.plugin.setup_device(device_info, self) + self.plugin.setup_device(device_info, self, purpose) + except OSError as e: + self.show_error(_('We encountered an error while connecting to your device:') + + '\n' + str(e) + '\n' + + _('To try to fix this, we will now re-pair with your device.') + '\n' + + _('Please try again.')) + devmgr = self.plugins.device_manager + devmgr.unpair_id(device_info.device.id_) + self.choose_hw_device(purpose) + return + except UserCancelled: + self.choose_hw_device(purpose) + return except BaseException as e: self.show_error(str(e)) - self.choose_hw_device() + self.choose_hw_device(purpose) return - if self.wallet_type=='multisig': - # There is no general standard for HD multisig. - # This is partially compatible with BIP45; assumes index=0 - self.on_hw_derivation(name, device_info, "m/45'/0") + if purpose == HWD_SETUP_NEW_WALLET: + if self.wallet_type=='multisig': + # There is no general standard for HD multisig. + # This is partially compatible with BIP45; assumes index=0 + self.on_hw_derivation(name, device_info, "m/45'/0") + else: + f = lambda x: self.run('on_hw_derivation', name, device_info, str(x)) + self.derivation_dialog(f) + elif purpose == HWD_SETUP_DECRYPT_WALLET: + derivation = get_derivation_used_for_hw_device_encryption() + xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self) + password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) + try: + self.storage.decrypt(password) + except InvalidPassword: + # try to clear session so that user can type another passphrase + devmgr = self.plugins.device_manager + client = devmgr.client_by_id(device_info.device.id_) + if hasattr(client, 'clear_session'): # FIXME not all hw wallet plugins have this + client.clear_session() + raise else: - f = lambda x: self.run('on_hw_derivation', name, device_info, str(x)) - self.derivation_dialog(f) + raise Exception('unknown purpose: %s' % purpose) def derivation_dialog(self, f): - default = bip44_derivation(0, False) + default = bip44_derivation(0, bip43_purpose=44) message = '\n'.join([ _('Enter your wallet derivation here.'), _('If you are not sure what this is, leave this field unchanged.') ]) - self.line_dialog(run_next=f, title=_('Derivation'), message=message, default=default, test=bitcoin.is_bip32_derivation) + presets = ( + ('legacy BIP44', bip44_derivation(0, bip43_purpose=44)), + ) + while True: + try: + self.line_dialog(run_next=f, title=_('Derivation'), message=message, + default=default, test=bitcoin.is_bip32_derivation, + presets=presets) + return + except ScriptTypeNotSupported as e: + self.show_error(e) + # let the user choose again def on_hw_derivation(self, name, device_info, derivation): from .keystore import hardware_keystore - xtype = 'standard' + xtype = keystore.xtype_from_derivation(derivation) try: xpub = self.plugin.get_xpub(device_info.device.id_, derivation, xtype, self) + except ScriptTypeNotSupported: + raise # this is handled in derivation_dialog except BaseException as e: self.show_error(e) return @@ -277,7 +346,7 @@ class BaseWizard(object): elif self.seed_type == 'old': self.run('create_keystore', seed, '') else: - raise BaseException('Unknown seed type', self.seed_type) + raise Exception('Unknown seed type', self.seed_type) def on_restore_bip39(self, seed, passphrase): f = lambda x: self.run('on_bip43', seed, passphrase, str(x)) @@ -330,13 +399,45 @@ class BaseWizard(object): self.run('create_wallet') def create_wallet(self): - if any(k.may_have_password() for k in self.keystores): - self.request_password(run_next=self.on_password) + encrypt_keystore = any(k.may_have_password() for k in self.keystores) + # note: the following condition ("if") is duplicated logic from + # wallet.get_available_storage_encryption_version() + if self.wallet_type == 'standard' and isinstance(self.keystores[0], keystore.Hardware_KeyStore): + # offer encrypting with a pw derived from the hw device + k = self.keystores[0] + try: + k.handler = self.plugin.create_handler(self) + password = k.get_password_for_storage_encryption() + except UserCancelled: + devmgr = self.plugins.device_manager + devmgr.unpair_xpub(k.xpub) + self.choose_hw_device() + return + except BaseException as e: + traceback.print_exc(file=sys.stderr) + self.show_error(str(e)) + return + self.request_storage_encryption( + run_next=lambda encrypt_storage: self.on_password( + password, + encrypt_storage=encrypt_storage, + storage_enc_version=STO_EV_XPUB_PW, + encrypt_keystore=False)) else: - self.on_password(None, False) + # prompt the user to set an arbitrary password + self.request_password( + run_next=lambda password, encrypt_storage: self.on_password( + password, + encrypt_storage=encrypt_storage, + storage_enc_version=STO_EV_USER_PW, + encrypt_keystore=encrypt_keystore), + force_disable_encrypt_cb=not encrypt_keystore) - def on_password(self, password, encrypt): - self.storage.set_password(password, encrypt) + def on_password(self, password, *, encrypt_storage, + storage_enc_version=STO_EV_USER_PW, encrypt_keystore): + self.storage.set_keystore_encryption(bool(password) and encrypt_keystore) + if encrypt_storage: + self.storage.set_password(password, enc_version=storage_enc_version) for k in self.keystores: if k.may_have_password(): k.update_password(None, password) @@ -352,18 +453,21 @@ class BaseWizard(object): self.storage.write() self.wallet = Multisig_Wallet(self.storage) self.run('create_addresses') + elif self.wallet_type == 'imported': + if len(self.keystores) > 0: + keys = self.keystores[0].dump() + self.storage.put('keystore', keys) + self.wallet = Imported_Wallet(self.storage) + self.wallet.storage.write() + self.terminate() def show_xpub_and_add_cosigners(self, xpub): self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) - def on_cosigner(self, text, password, i): - k = keystore.from_master_key(text, password) - self.on_keystore(k) - def choose_seed_type(self): title = _('Choose Seed type') message = ' '.join([ - "The type of addresses used by your wallet will depend on your seed.", + _("The type of addresses used by your wallet will depend on your seed."), ]) choices = [ ('create_standard_seed', _('Standard')), @@ -408,5 +512,5 @@ class BaseWizard(object): self.wallet.synchronize() self.wallet.storage.write() self.terminate() - msg = _("Electrum-Zcash is generating your addresses, please wait.") + msg = _("Electrum-Zcash is generating your addresses, please wait...") self.waiting_dialog(task, msg) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index d38730bc..929553e7 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -32,72 +32,18 @@ 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 constants -def read_json_dict(filename): - path = os.path.join(os.path.dirname(__file__), filename) - try: - with open(path, 'r') as f: - r = json.loads(f.read()) - except: - r = {} - return r - - - - -# Version numbers for BIP32 extended keys -# standard: xprv, xpub -XPRV_HEADERS = { - 'standard': 0x0488ade4, -} -XPUB_HEADERS = { - 'standard': 0x0488b21e, -} - - -class NetworkConstants: - - @classmethod - def set_mainnet(cls): - cls.TESTNET = False - cls.WIF_PREFIX = 0x80 - cls.ADDRTYPE_P2PKH = bytes.fromhex('1CB8') - cls.ADDRTYPE_P2SH = bytes.fromhex('1CBD') - cls.HEADERS_URL = 'https://github.com/zebra-lucky/electrum-zcash/releases/download/3.0.6/blockchain_headers' - cls.GENESIS = '00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08' - cls.DEFAULT_PORTS = {'t': '50001', 's': '50002'} - cls.DEFAULT_SERVERS = read_json_dict('servers.json') - XPRV_HEADERS['standard'] = 0x0488ade4 - XPUB_HEADERS['standard'] = 0x0488b21e - - @classmethod - def set_testnet(cls): - cls.TESTNET = True - cls.WIF_PREFIX = 0xEF - cls.ADDRTYPE_P2PKH = bytes.fromhex('1D25') - cls.ADDRTYPE_P2SH = bytes.fromhex('1CBA') - cls.HEADERS_URL = 'https://github.com/zebra-lucky/electrum-zcash/releases/download/3.0.6/blockchain_headers_testnet' - cls.GENESIS = '05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38' - cls.DEFAULT_PORTS = {'t':'51001', 's':'51002'} - cls.DEFAULT_SERVERS = read_json_dict('servers_testnet.json') - XPRV_HEADERS['standard'] = 0x04358394 - XPUB_HEADERS['standard'] = 0x043587CF - - -NetworkConstants.set_mainnet() ################################## transactions -MAX_FEE_RATE = 10000 -FEE_TARGETS = [25, 10, 5, 2] - COINBASE_MATURITY = 100 COIN = 100000000 -# supported types of transction outputs +# supported types of transaction outputs TYPE_ADDRESS = 0 TYPE_PUBKEY = 1 TYPE_SCRIPT = 2 @@ -196,7 +142,11 @@ def rev_hex(s): def int_to_hex(i, length=1): - assert isinstance(i, int) + if not isinstance(i, int): + raise TypeError('{} instead of int'.format(i)) + if i < 0: + # two's complement + i = pow(256, length) + i s = hex(i)[2:].rstrip('L') s = "0"*(2*length - len(s)) + s return rev_hex(s) @@ -215,11 +165,11 @@ def var_int(i): def op_push(i): - if i<0x4c: + if i<0x4c: # OP_PUSHDATA1 return int_to_hex(i) - elif i<0xff: + elif i<=0xff: return '4c' + int_to_hex(i) - elif i<0xffff: + elif i<=0xffff: return '4d' + int_to_hex(i,2) else: return '4e' + int_to_hex(i,4) @@ -322,11 +272,15 @@ def b58_address_to_hash160(addr): return _bytes[0:2], _bytes[2:22] -def hash160_to_p2pkh(h160): - return hash160_to_b58_address(h160, NetworkConstants.ADDRTYPE_P2PKH) +def hash160_to_p2pkh(h160, *, net=None): + if net is None: + net = constants.net + return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH) -def hash160_to_p2sh(h160): - return hash160_to_b58_address(h160, NetworkConstants.ADDRTYPE_P2SH) +def hash160_to_p2sh(h160, *, net=None): + if net is None: + net = constants.net + return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH) def public_key_to_p2pkh(public_key): return hash160_to_p2pkh(hash_160(public_key)) @@ -344,24 +298,26 @@ def redeem_script_to_address(txin_type, redeem_script): raise NotImplementedError(txin_type) -def script_to_address(script): +def script_to_address(script, *, net=None): from .transaction import get_address_from_output_script - t, addr = get_address_from_output_script(bfh(script)) + t, addr = get_address_from_output_script(bfh(script), net=net) assert t == TYPE_ADDRESS return addr -def address_to_script(addr): +def address_to_script(addr, *, net=None): + if net is None: + net = constants.net addrtype, hash_160 = b58_address_to_hash160(addr) - if addrtype == NetworkConstants.ADDRTYPE_P2PKH: + if addrtype == net.ADDRTYPE_P2PKH: script = '76a9' # op_dup, op_hash_160 script += push_script(bh2u(hash_160)) script += '88ac' # op_equalverify, op_checksig - elif addrtype == NetworkConstants.ADDRTYPE_P2SH: + elif addrtype == net.ADDRTYPE_P2SH: script = 'a9' # op_hash_160 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): @@ -387,7 +343,8 @@ assert len(__b43chars) == 43 def base_encode(v, base): """ encode v, which is a string of bytes, to base58.""" assert_bytes(v) - assert base in (58, 43) + if base not in (58, 43): + raise ValueError('not supported base: {}'.format(base)) chars = __b58chars if base == 43: chars = __b43chars @@ -417,13 +374,17 @@ def base_decode(v, length, base): """ decode v into a string of len bytes.""" # assert_bytes(v) v = to_bytes(v, 'ascii') - assert base in (58, 43) + if base not in (58, 43): + raise ValueError('not supported base: {}'.format(base)) chars = __b58chars if base == 43: chars = __b43chars long_value = 0 for (i, c) in enumerate(v[::-1]): - long_value += chars.find(bytes([c])) * (base**i) + digit = chars.find(bytes([c])) + if digit == -1: + raise ValueError('Forbidden character {} for base {}'.format(c, base)) + long_value += digit * (base**i) result = bytearray() while long_value >= 256: div, mod = divmod(long_value, 256) @@ -443,6 +404,10 @@ def base_decode(v, length, base): return bytes(result) +class InvalidChecksum(Exception): + pass + + def EncodeBase58Check(vchIn): hash = Hash(vchIn) return base_encode(vchIn + hash[0:4], base=58) @@ -455,37 +420,64 @@ def DecodeBase58Check(psz): hash = Hash(key) cs32 = hash[0:4] if cs32 != csum: - return None + raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum))) else: return key +# backwards compat +# extended WIF for segwit (used in 3.0.x; but still used internally) +# the keys in this dict should be a superset of what Imported Wallets can import SCRIPT_TYPES = { 'p2pkh':0, 'p2sh':5, } -def serialize_privkey(secret, compressed, txin_type): - prefix = bytes([(SCRIPT_TYPES[txin_type]+NetworkConstants.WIF_PREFIX)&255]) +def serialize_privkey(secret, compressed, txin_type, internal_use=False): + if internal_use: + prefix = bytes([(SCRIPT_TYPES[txin_type] + constants.net.WIF_PREFIX) & 255]) + else: + prefix = bytes([constants.net.WIF_PREFIX]) suffix = b'\01' if compressed else b'' vchIn = prefix + secret + suffix - return EncodeBase58Check(vchIn) + base58_wif = EncodeBase58Check(vchIn) + if internal_use: + return base58_wif + else: + return '{}:{}'.format(txin_type, base58_wif) def deserialize_privkey(key): - # whether the pubkey is compressed should be visible from the keystore - vch = DecodeBase58Check(key) if is_minikey(key): return 'p2pkh', minikey_to_private_key(key), True - elif vch: - txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - NetworkConstants.WIF_PREFIX] - assert len(vch) in [33, 34] - compressed = len(vch) == 34 - return txin_type, vch[1:33], compressed + + txin_type = None + if ':' in key: + txin_type, key = key.split(sep=':', maxsplit=1) + if txin_type not in SCRIPT_TYPES: + raise BitcoinException('unknown script type: {}'.format(txin_type)) + try: + vch = DecodeBase58Check(key) + except BaseException: + neutered_privkey = str(key)[:3] + '..' + str(key)[-2:] + 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 + txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - constants.net.WIF_PREFIX] else: - raise BaseException("cannot deserialize", key) + # all other keys must have a fixed first byte + if vch[0] != constants.net.WIF_PREFIX: + raise BitcoinException('invalid prefix ({}) for WIF key'.format(vch[0])) + + if len(vch) not in [33, 34]: + raise BitcoinException('invalid vch len for WIF key: {}'.format(len(vch))) + compressed = len(vch) == 34 + return txin_type, vch[1:33], compressed + def regenerate_key(pk): assert len(pk) == 32 @@ -519,7 +511,7 @@ def is_b58_address(addr): addrtype, h = b58_address_to_hash160(addr) except Exception as e: return False - if addrtype not in [NetworkConstants.ADDRTYPE_P2PKH, NetworkConstants.ADDRTYPE_P2SH]: + if addrtype not in [constants.net.ADDRTYPE_P2PKH, constants.net.ADDRTYPE_P2SH]: return False return addr == hash160_to_b58_address(h, addrtype) @@ -582,8 +574,8 @@ def verify_message(address, sig, message): return False -def encrypt_message(message, pubkey): - return EC_KEY.encrypt_message(message, bfh(pubkey)) +def encrypt_message(message, pubkey, magic=b'BIE1'): + return EC_KEY.encrypt_message(message, bfh(pubkey), magic) def chunks(l, n): @@ -728,7 +720,7 @@ class EC_KEY(object): # ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac @classmethod - def encrypt_message(self, message, pubkey): + def encrypt_message(self, message, pubkey, magic=b'BIE1'): assert_bytes(message) pk = ser_to_point(pubkey) @@ -742,20 +734,20 @@ class EC_KEY(object): iv, key_e, key_m = key[0:16], key[16:32], key[32:] ciphertext = aes_encrypt_with_iv(key_e, iv, message) ephemeral_pubkey = bfh(ephemeral.get_public_key(compressed=True)) - encrypted = b'BIE1' + ephemeral_pubkey + ciphertext + encrypted = magic + ephemeral_pubkey + ciphertext mac = hmac.new(key_m, encrypted, hashlib.sha256).digest() return base64.b64encode(encrypted + mac) - def decrypt_message(self, encrypted): + def decrypt_message(self, encrypted, magic=b'BIE1'): encrypted = base64.b64decode(encrypted) if len(encrypted) < 85: raise Exception('invalid ciphertext: length') - magic = encrypted[:4] + magic_found = encrypted[:4] ephemeral_pubkey = encrypted[4:37] ciphertext = encrypted[37:-32] mac = encrypted[-32:] - if magic != b'BIE1': + if magic_found != magic: raise Exception('invalid ciphertext: invalid magic bytes') try: ephemeral_pubkey = ser_to_point(ephemeral_pubkey) @@ -831,47 +823,60 @@ def _CKD_pub(cK, c, s): return cK_n, c_n -def xprv_header(xtype): - return bfh("%08x" % XPRV_HEADERS[xtype]) +def xprv_header(xtype, *, net=None): + if net is None: + net = constants.net + return bfh("%08x" % net.XPRV_HEADERS[xtype]) -def xpub_header(xtype): - return bfh("%08x" % XPUB_HEADERS[xtype]) +def xpub_header(xtype, *, net=None): + if net is None: + net = constants.net + return bfh("%08x" % net.XPUB_HEADERS[xtype]) -def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4, child_number=b'\x00'*4): - xprv = xprv_header(xtype) + bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k +def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4, + child_number=b'\x00'*4, *, net=None): + xprv = xprv_header(xtype, net=net) \ + + bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k return EncodeBase58Check(xprv) -def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4, child_number=b'\x00'*4): - xpub = xpub_header(xtype) + bytes([depth]) + fingerprint + child_number + c + cK +def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4, + child_number=b'\x00'*4, *, net=None): + xpub = xpub_header(xtype, net=net) \ + + bytes([depth]) + fingerprint + child_number + c + cK return EncodeBase58Check(xpub) -def deserialize_xkey(xkey, prv): +def deserialize_xkey(xkey, prv, *, net=None): + if net is 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] c = xkey[13:13+32] header = int('0x' + bh2u(xkey[0:4]), 16) - headers = XPRV_HEADERS if prv else XPUB_HEADERS + 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:] return xtype, depth, fingerprint, child_number, c, K_or_k -def deserialize_xpub(xkey): - return deserialize_xkey(xkey, False) -def deserialize_xprv(xkey): - return deserialize_xkey(xkey, True) +def deserialize_xpub(xkey, *, net=None): + return deserialize_xkey(xkey, False, net=net) + +def deserialize_xprv(xkey, *, net=None): + return deserialize_xkey(xkey, True, net=net) def xpub_type(x): return deserialize_xpub(x)[0] @@ -915,7 +920,8 @@ def xpub_from_pubkey(xtype, cK): def bip32_derivation(s): - assert s.startswith('m/') + if not s.startswith('m/'): + raise ValueError('invalid bip32 derivation path: {}'.format(s)) s = s[2:] for n in s.split('/'): if n == '': continue @@ -930,7 +936,9 @@ def is_bip32_derivation(x): return False def bip32_private_derivation(xprv, branch, sequence): - assert sequence.startswith(branch) + if not sequence.startswith(branch): + raise ValueError('incompatible branch ({}) and sequence ({})' + .format(branch, sequence)) if branch == sequence: return xprv, xpub_from_xprv(xprv) xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv) @@ -952,7 +960,9 @@ def bip32_private_derivation(xprv, branch, sequence): def bip32_public_derivation(xpub, branch, sequence): xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub) - assert sequence.startswith(branch) + if not sequence.startswith(branch): + raise ValueError('incompatible branch ({}) and sequence ({})' + .format(branch, sequence)) sequence = sequence[len(branch):] for n in sequence.split('/'): if n == '': continue diff --git a/lib/blockchain.py b/lib/blockchain.py index 841a3581..28d242ee 100644 --- a/lib/blockchain.py +++ b/lib/blockchain.py @@ -25,79 +25,33 @@ import threading from . import util from . import bitcoin +from . import constants from .bitcoin import * - -HDR_LEN = 1487 -CHUNK_LEN = 100 - -POW_LIMIT = 0x0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -POW_AVERAGING_WINDOW = 17 -POW_MEDIAN_BLOCK_SPAN = 11 -POW_MAX_ADJUST_DOWN = 32 -POW_MAX_ADJUST_UP = 16 -POW_DAMPING_FACTOR = 4 -POW_TARGET_SPACING = 150 - -AVERAGING_WINDOW_TIMESPAN = POW_AVERAGING_WINDOW * POW_TARGET_SPACING - -MIN_ACTUAL_TIMESPAN = AVERAGING_WINDOW_TIMESPAN * \ - (100 - POW_MAX_ADJUST_UP) // 100 - -MAX_ACTUAL_TIMESPAN = AVERAGING_WINDOW_TIMESPAN * \ - (100 + POW_MAX_ADJUST_DOWN) // 100 - - -def bits_to_target(bits): - """Convert a compact representation to a hex target.""" - MM = 256 * 256 * 256 - a = bits % MM - if a < 0x8000: - a *= 256 - target = a * pow(2, 8 * (bits // MM - 3)) - return target - -def target_to_bits(target): - """Convert a target to compact representation.""" - MM = 256 * 256 * 256 - c = ('%064X' % target)[2:] - i = 31 - while c[0:2] == '00': - c = c[2:] - i -= 1 - - c = int('0x%s' % c[0:6], 16) - if c >= 0x800000: - c //= 256 - i += 1 - - new_bits = c + MM * i - return new_bits +MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 def serialize_header(res): s = int_to_hex(res.get('version'), 4) \ + rev_hex(res.get('prev_block_hash')) \ + rev_hex(res.get('merkle_root')) \ - + rev_hex(res.get('reserved_hash')) \ + int_to_hex(int(res.get('timestamp')), 4) \ + int_to_hex(int(res.get('bits')), 4) \ - + rev_hex(res.get('nonce')) \ - + rev_hex(res.get('sol_size')) \ - + rev_hex(res.get('solution')) + + int_to_hex(int(res.get('nonce')), 4) return s def deserialize_header(s, height): + if not s: + raise Exception('Invalid header: {}'.format(s)) + if len(s) != 80: + raise Exception('Invalid header length: {}'.format(len(s))) hex_to_int = lambda s: int('0x' + bh2u(s[::-1]), 16) h = {} h['version'] = hex_to_int(s[0:4]) h['prev_block_hash'] = hash_encode(s[4:36]) h['merkle_root'] = hash_encode(s[36:68]) - h['reserved_hash'] = hash_encode(s[68:100]) - h['timestamp'] = hex_to_int(s[100:104]) - h['bits'] = hex_to_int(s[104:108]) - h['nonce'] = hash_encode(s[108:140]) - h['sol_size'] = hash_encode(s[140:143]) - h['solution'] = hash_encode(s[143:1487]) + h['timestamp'] = hex_to_int(s[68:72]) + h['bits'] = hex_to_int(s[72:76]) + h['nonce'] = hex_to_int(s[76:80]) h['block_height'] = height return h @@ -122,7 +76,11 @@ def read_blockchains(config): checkpoint = int(filename.split('_')[2]) parent_id = int(filename.split('_')[1]) b = Blockchain(config, checkpoint, parent_id) - blockchains[b.checkpoint] = b + h = b.read_header(b.checkpoint) + if b.parent().can_connect(h, check_height=False): + blockchains[b.checkpoint] = b + else: + util.print_error("cannot connect", filename) return blockchains def check_header(header): @@ -149,6 +107,7 @@ class Blockchain(util.PrintError): self.config = config self.catch_up = None # interface catching up self.checkpoint = checkpoint + self.checkpoints = constants.net.CHECKPOINTS self.parent_id = parent_id self.lock = threading.Lock() with self.lock: @@ -192,35 +151,29 @@ class Blockchain(util.PrintError): def update_size(self): p = self.path() - self._size = os.path.getsize(p)//HDR_LEN if os.path.exists(p) else 0 + self._size = os.path.getsize(p)//80 if os.path.exists(p) else 0 - def verify_header(self, header, prev_header, bits, target): - prev_hash = hash_header(prev_header) + def verify_header(self, header, prev_hash, target): _hash = hash_header(header) if prev_hash != header.get('prev_block_hash'): - raise BaseException("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))) - if bitcoin.NetworkConstants.TESTNET: + raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))) + if constants.net.TESTNET: return + bits = self.target_to_bits(target) if bits != header.get('bits'): - raise BaseException("bits mismatch: %s vs %s" % (bits, header.get('bits'))) + raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits'))) if int('0x' + _hash, 16) > target: - raise BaseException("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target)) + raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target)) def verify_chunk(self, index, data): - num = len(data) // HDR_LEN - prev_header = None - if index != 0: - prev_header = self.read_header(index * CHUNK_LEN - 1) - chain = [] + num = len(data) // 80 + prev_hash = self.get_hash(index * 2016 - 1) + target = self.get_target(index-1) for i in range(num): - raw_header = data[i*HDR_LEN:(i+1) * HDR_LEN] - header = deserialize_header(raw_header, index*CHUNK_LEN + i) - height = index * CHUNK_LEN + i - header['block_height'] = height - chain.append(header) - bits, target = self.get_target(height, chain) - self.verify_header(header, prev_header, bits, target) - prev_header = header + raw_header = data[i*80:(i+1) * 80] + header = deserialize_header(raw_header, index*2016 + i) + self.verify_header(header, prev_hash, target) + prev_hash = hash_header(header) def path(self): d = util.get_headers_dir(self.config) @@ -229,11 +182,12 @@ class Blockchain(util.PrintError): def save_chunk(self, index, chunk): filename = self.path() - d = (index * CHUNK_LEN - self.checkpoint) * HDR_LEN + d = (index * 2016 - self.checkpoint) * 80 if d < 0: chunk = chunk[-d:] d = 0 - self.write(chunk, d) + truncate = index >= len(self.checkpoints) + self.write(chunk, d, truncate) self.swap_with_parent() def swap_with_parent(self): @@ -249,10 +203,10 @@ class Blockchain(util.PrintError): with open(self.path(), 'rb') as f: my_data = f.read() with open(parent.path(), 'rb') as f: - f.seek((checkpoint - parent.checkpoint)*HDR_LEN) - parent_data = f.read(parent_branch_size*HDR_LEN) + f.seek((checkpoint - parent.checkpoint)*80) + parent_data = f.read(parent_branch_size*80) self.write(parent_data, 0) - parent.write(my_data, (checkpoint - parent.checkpoint)*HDR_LEN) + parent.write(my_data, (checkpoint - parent.checkpoint)*80) # store file path for b in blockchains.values(): b.old_path = b.path() @@ -270,11 +224,11 @@ class Blockchain(util.PrintError): blockchains[self.checkpoint] = self blockchains[parent.checkpoint] = parent - def write(self, data, offset): + def write(self, data, offset, truncate=True): filename = self.path() with self.lock: with open(filename, 'rb+') as f: - if offset != self._size*HDR_LEN: + if truncate and offset != self._size*80: f.seek(offset) f.truncate() f.seek(offset) @@ -287,8 +241,8 @@ class Blockchain(util.PrintError): delta = header.get('block_height') - self.checkpoint data = bfh(serialize_header(header)) assert delta == self.size() - assert len(data) == HDR_LEN - self.write(data, delta*HDR_LEN) + assert len(data) == 80 + self.write(data, delta*80) self.swap_with_parent() def read_header(self, height): @@ -303,88 +257,90 @@ class Blockchain(util.PrintError): name = self.path() if os.path.exists(name): with open(name, 'rb') as f: - f.seek(delta * HDR_LEN) - h = f.read(HDR_LEN) + f.seek(delta * 80) + h = f.read(80) + if len(h) < 80: + raise Exception('Expected to read a full header. This was only {} bytes'.format(len(h))) + elif not os.path.exists(util.get_headers_dir(self.config)): + raise Exception('Electrum datadir does not exist. Was it deleted while running?') + else: + raise Exception('Cannot find headers file but datadir is there. Should be at {}'.format(name)) + if h == bytes([0])*80: + return None return deserialize_header(h, height) def get_hash(self, height): - return hash_header(self.read_header(height)) + if height == -1: + return '0000000000000000000000000000000000000000000000000000000000000000' + elif height == 0: + return constants.net.GENESIS + elif height < len(self.checkpoints) * 2016: + assert (height+1) % 2016 == 0, height + index = height // 2016 + h, t = self.checkpoints[index] + return h + else: + return hash_header(self.read_header(height)) - def get_median_time(self, height, chain=None): - if chain is None: - chain = [] + def get_target(self, index): + # compute target from chunk x, used in chunk x+1 + if constants.net.TESTNET: + return 0 + if index == -1: + return MAX_TARGET + if index < len(self.checkpoints): + h, t = self.checkpoints[index] + return t + # new target + first = self.read_header(index * 2016) + last = self.read_header(index * 2016 + 2015) + bits = last.get('bits') + target = self.bits_to_target(bits) + nActualTimespan = last.get('timestamp') - first.get('timestamp') + nTargetTimespan = 14 * 24 * 60 * 60 + nActualTimespan = max(nActualTimespan, nTargetTimespan // 4) + nActualTimespan = min(nActualTimespan, nTargetTimespan * 4) + new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan) + return new_target - height_range = range(max(0, height - POW_MEDIAN_BLOCK_SPAN), - max(1, height)) - median = [] - for h in height_range: - header = self.read_header(h) - if not header: - for header in chain: - if header.get('block_height') == h: - break - assert header and header.get('block_height') == h - median.append(header.get('timestamp')) + def bits_to_target(self, bits): + bitsN = (bits >> 24) & 0xff + if not (bitsN >= 0x03 and bitsN <= 0x1d): + raise Exception("First part of bits should be in [0x03, 0x1d]") + bitsBase = bits & 0xffffff + if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff): + raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") + return bitsBase << (8 * (bitsN-3)) - median.sort() - return median[len(median)//2]; - - def get_target(self, height, chain=None): - if chain is None: - chain = [] - - if bitcoin.NetworkConstants.TESTNET: - return 0, 0 - - if height <= POW_AVERAGING_WINDOW: - return target_to_bits(POW_LIMIT), POW_LIMIT - - height_range = range(max(0, height - POW_AVERAGING_WINDOW), - max(1, height)) - mean_target = 0 - for h in height_range: - header = self.read_header(h) - if not header: - for header in chain: - if header.get('block_height') == h: - break - assert header and header.get('block_height') == h - mean_target += bits_to_target(header.get('bits')) - mean_target //= POW_AVERAGING_WINDOW - - actual_timespan = self.get_median_time(height, chain) - \ - self.get_median_time(height - POW_AVERAGING_WINDOW, chain) - actual_timespan = AVERAGING_WINDOW_TIMESPAN + \ - int((actual_timespan - AVERAGING_WINDOW_TIMESPAN) / \ - POW_DAMPING_FACTOR) - if actual_timespan < MIN_ACTUAL_TIMESPAN: - actual_timespan = MIN_ACTUAL_TIMESPAN - elif actual_timespan > MAX_ACTUAL_TIMESPAN: - actual_timespan = MAX_ACTUAL_TIMESPAN - - next_target = mean_target // AVERAGING_WINDOW_TIMESPAN * actual_timespan - - if next_target > POW_LIMIT: - next_target = POW_LIMIT - - return target_to_bits(next_target), next_target + def target_to_bits(self, target): + c = ("%064x" % target)[2:] + while c[:2] == '00' and len(c) > 6: + c = c[2:] + bitsN, bitsBase = len(c) // 2, int('0x' + c[:6], 16) + if bitsBase >= 0x800000: + bitsN += 1 + bitsBase >>= 8 + return bitsN << 24 | bitsBase def can_connect(self, header, check_height=True): + if header is None: + return False height = header['block_height'] if check_height and self.height() != height - 1: + #self.print_error("cannot connect at height", height) return False if height == 0: - return hash_header(header) == bitcoin.NetworkConstants.GENESIS - previous_header = self.read_header(height -1) - if not previous_header: + return hash_header(header) == constants.net.GENESIS + try: + prev_hash = self.get_hash(height - 1) + except: return False - prev_hash = hash_header(previous_header) if prev_hash != header.get('prev_block_hash'): return False - bits, target = self.get_target(height) + target = self.get_target(height // 2016 - 1) try: - self.verify_header(header, previous_header, bits, target) - except: + self.verify_header(header, prev_hash, target) + except BaseException as e: return False return True @@ -396,5 +352,15 @@ class Blockchain(util.PrintError): self.save_chunk(idx, data) return True except BaseException as e: - self.print_error('verify_chunk failed', str(e)) + self.print_error('verify_chunk %d failed'%idx, str(e)) return False + + def get_checkpoints(self): + # for each chunk, store the hash of the last block and the target after the chunk + cp = [] + n = self.height() // 2016 + for index in range(n): + h = self.get_hash((index+1) * 2016 -1) + target = self.get_target(index) + cp.append((h, target)) + return cp diff --git a/lib/coinchooser.py b/lib/coinchooser.py index 37b975c5..5d13b93e 100644 --- a/lib/coinchooser.py +++ b/lib/coinchooser.py @@ -25,7 +25,7 @@ from collections import defaultdict, namedtuple from math import floor, log10 -from .bitcoin import sha256, COIN, TYPE_ADDRESS +from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address from .transaction import Transaction from .util import NotEnoughFunds, PrintError @@ -68,7 +68,12 @@ class PRNG: x[i], x[j] = x[j], x[i] -Bucket = namedtuple('Bucket', ['desc', 'size', 'value', 'coins']) +Bucket = namedtuple('Bucket', + ['desc', + 'weight', # as in BIP-141 + 'value', # in satoshis + 'coins', # UTXOs + 'min_height']) # min block height where a coin was confirmed def strip_unneeded(bkts, sufficient_funds): '''Remove buckets that are unnecessary in achieving the spend amount''' @@ -81,6 +86,8 @@ def strip_unneeded(bkts, sufficient_funds): class CoinChooserBase(PrintError): + enable_output_value_rounding = False + def keys(self, coins): raise NotImplementedError @@ -92,10 +99,10 @@ class CoinChooserBase(PrintError): def make_Bucket(desc, coins): weight = sum(Transaction.estimated_input_weight(coin) - for coin in coins) - size = Transaction.virtual_size_from_weight(weight) + for coin in coins) value = sum(coin['value'] for coin in coins) - return Bucket(desc, size, value, coins) + min_height = min(coin['height'] for coin in coins) + return Bucket(desc, weight, value, coins, min_height) return list(map(make_Bucket, buckets.keys(), buckets.values())) @@ -126,7 +133,13 @@ class CoinChooserBase(PrintError): zeroes = [trailing_zeroes(i) for i in output_amounts] min_zeroes = min(zeroes) max_zeroes = max(zeroes) - zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1) + + if n > 1: + zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1) + else: + # if there is only one change output, this will ensure that we aim + # to have one that is exactly as precise as the most precise output + zeroes = [min_zeroes] # Calculate change; randomize it a bit if using more than 1 output remaining = change_amount @@ -141,8 +154,10 @@ class CoinChooserBase(PrintError): n -= 1 # Last change output. Round down to maximum precision but lose - # no more than 100 satoshis to fees (2dp) - N = pow(10, min(2, zeroes[0])) + # no more than 10**max_dp_to_round_for_privacy + # e.g. a max of 2 decimal places means losing 100 satoshis to fees + max_dp_to_round_for_privacy = 2 if self.enable_output_value_rounding else 0 + N = pow(10, min(max_dp_to_round_for_privacy, zeroes[0])) amount = (remaining // N) * N amounts.append(amount) @@ -168,27 +183,40 @@ class CoinChooserBase(PrintError): def make_tx(self, coins, outputs, change_addrs, fee_estimator, dust_threshold): - '''Select unspent coins to spend to pay outputs. If the change is + """Select unspent coins to spend to pay outputs. If the change is greater than dust_threshold (after adding the change output to the transaction) it is kept, otherwise none is sent and it is - added to the transaction fee.''' + added to the transaction fee. + + Note: fee_estimator expects virtual bytes + """ # Deterministic randomness from coins utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins] self.p = PRNG(''.join(sorted(utxos))) - # Copy the ouputs so when adding change we don't modify "outputs" + # Copy the outputs so when adding change we don't modify "outputs" tx = Transaction.from_io([], outputs[:]) - # Size of the transaction with no inputs and no change - base_size = tx.estimated_size() + # Weight of the transaction with no inputs and no change + # Note: this will use legacy tx serialization. The only side effect + # should be that the marker and flag are excluded, which is + # compensated in get_tx_weight() + base_weight = tx.estimated_weight() spent_amount = tx.output_value() + def fee_estimator_w(weight): + return fee_estimator(Transaction.virtual_size_from_weight(weight)) + + def get_tx_weight(buckets): + total_weight = base_weight + sum(bucket.weight for bucket in buckets) + return total_weight + def sufficient_funds(buckets): '''Given a list of buckets, return True if it has enough value to pay for the transaction''' total_input = sum(bucket.value for bucket in buckets) - total_size = sum(bucket.size for bucket in buckets) + base_size - return total_input >= spent_amount + fee_estimator(total_size) + total_weight = get_tx_weight(buckets) + return total_input >= spent_amount + fee_estimator_w(total_weight) # Collect the coins into buckets, choose a subset of the buckets buckets = self.bucketize_coins(coins) @@ -196,11 +224,18 @@ class CoinChooserBase(PrintError): self.penalty_func(tx)) tx.add_inputs([coin for b in buckets for coin in b.coins]) - tx_size = base_size + sum(bucket.size for bucket in buckets) + tx_weight = get_tx_weight(buckets) - # This takes a count of change outputs and returns a tx fee; - # each pay-to-bitcoin-address output serializes as 34 bytes - fee = lambda count: fee_estimator(tx_size + count * 34) + # change is sent back to sending address unless specified + if not change_addrs: + change_addrs = [tx.inputs()[0]['address']] + # note: this is not necessarily the final "first input address" + # because the inputs had not been sorted at this point + assert is_address(change_addrs[0]) + + # This takes a count of change outputs and returns a tx fee + output_weight = 4 * Transaction.estimated_output_size(change_addrs[0]) + fee = lambda count: fee_estimator_w(tx_weight + count * output_weight) change = self.change_outputs(tx, change_addrs, fee, dust_threshold) tx.add_outputs(change) @@ -212,35 +247,14 @@ class CoinChooserBase(PrintError): def choose_buckets(self, buckets, sufficient_funds, penalty_func): raise NotImplemented('To be subclassed') -class CoinChooserOldestFirst(CoinChooserBase): - '''Maximize transaction priority. Select the oldest unspent - transaction outputs in your wallet, that are sufficient to cover - the spent amount. Then, remove any unneeded inputs, starting with - the smallest in value. - ''' - - def keys(self, coins): - return [coin['prevout_hash'] + ':' + str(coin['prevout_n']) - for coin in coins] - - def choose_buckets(self, buckets, sufficient_funds, penalty_func): - '''Spend the oldest buckets first.''' - # Unconfirmed coins are young, not old - adj_height = lambda height: 99999999 if height <= 0 else height - buckets.sort(key = lambda b: max(adj_height(coin['height']) - for coin in b.coins)) - selected = [] - for bucket in buckets: - selected.append(bucket) - if sufficient_funds(selected): - return strip_unneeded(selected, sufficient_funds) - else: - raise NotEnoughFunds() class CoinChooserRandom(CoinChooserBase): - def bucket_candidates(self, buckets, sufficient_funds): + def bucket_candidates_any(self, buckets, sufficient_funds): '''Returns a list of bucket sets.''' + if not buckets: + raise NotEnoughFunds() + candidates = set() # Add all singletons @@ -262,13 +276,49 @@ class CoinChooserRandom(CoinChooserBase): candidates.add(tuple(sorted(permutation[:count + 1]))) break else: + # FIXME this assumes that the effective value of any bkt is >= 0 + # we should make sure not to choose buckets with <= 0 eff. val. raise NotEnoughFunds() candidates = [[buckets[n] for n in c] for c in candidates] return [strip_unneeded(c, sufficient_funds) for c in candidates] + def bucket_candidates_prefer_confirmed(self, buckets, sufficient_funds): + """Returns a list of bucket sets preferring confirmed coins. + + Any bucket can be: + 1. "confirmed" if it only contains confirmed coins; else + 2. "unconfirmed" if it does not contain coins with unconfirmed parents + 3. other: e.g. "unconfirmed parent" or "local" + + This method tries to only use buckets of type 1, and if the coins there + are not enough, tries to use the next type but while also selecting + all buckets of all previous types. + """ + conf_buckets = [bkt for bkt in buckets if bkt.min_height > 0] + unconf_buckets = [bkt for bkt in buckets if bkt.min_height == 0] + other_buckets = [bkt for bkt in buckets if bkt.min_height < 0] + + bucket_sets = [conf_buckets, unconf_buckets, other_buckets] + already_selected_buckets = [] + + for bkts_choose_from in bucket_sets: + try: + def sfunds(bkts): + return sufficient_funds(already_selected_buckets + bkts) + + candidates = self.bucket_candidates_any(bkts_choose_from, sfunds) + break + except NotEnoughFunds: + already_selected_buckets += bkts_choose_from + else: + raise NotEnoughFunds() + + candidates = [(already_selected_buckets + c) for c in candidates] + return [strip_unneeded(c, sufficient_funds) for c in candidates] + def choose_buckets(self, buckets, sufficient_funds, penalty_func): - candidates = self.bucket_candidates(buckets, sufficient_funds) + candidates = self.bucket_candidates_prefer_confirmed(buckets, sufficient_funds) penalties = [penalty_func(cand) for cand in candidates] winner = candidates[penalties.index(min(penalties))] self.print_error("Bucket sets:", len(buckets)) @@ -276,14 +326,15 @@ class CoinChooserRandom(CoinChooserBase): return winner class CoinChooserPrivacy(CoinChooserRandom): - '''Attempts to better preserve user privacy. First, if any coin is - spent from a user address, all coins are. Compared to spending - from other addresses to make up an amount, this reduces + """Attempts to better preserve user privacy. + First, if any coin is spent from a user address, all coins are. + Compared to spending from other addresses to make up an amount, this reduces information leakage about sender holdings. It also helps to reduce blockchain UTXO bloat, and reduce future privacy loss that - would come from reusing that address' remaining UTXOs. Second, it - penalizes change that is quite different to the sent amount. - Third, it penalizes change that is too big.''' + would come from reusing that address' remaining UTXOs. + Second, it penalizes change that is quite different to the sent amount. + Third, it penalizes change that is too big. + """ def keys(self, coins): return [coin['address'] for coin in coins] @@ -296,6 +347,7 @@ class CoinChooserPrivacy(CoinChooserRandom): def penalty(buckets): badness = len(buckets) - 1 total_input = sum(bucket.value for bucket in buckets) + # FIXME "change" here also includes fees change = float(total_input - spent_amount) # Penalize change not roughly in output range if change < min_change: @@ -309,15 +361,18 @@ class CoinChooserPrivacy(CoinChooserRandom): return penalty -COIN_CHOOSERS = {'Priority': CoinChooserOldestFirst, - 'Privacy': CoinChooserPrivacy} +COIN_CHOOSERS = { + 'Privacy': CoinChooserPrivacy, +} def get_name(config): kind = config.get('coin_chooser') if not kind in COIN_CHOOSERS: - kind = 'Priority' + kind = 'Privacy' return kind def get_coin_chooser(config): klass = COIN_CHOOSERS[get_name(config)] - return klass() + coinchooser = klass() + coinchooser.enable_output_value_rounding = config.get('coin_chooser_output_rounding', False) + return coinchooser diff --git a/lib/commands.py b/lib/commands.py index e20f2097..618bcfa8 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -34,7 +34,7 @@ from functools import wraps from decimal import Decimal from .import util -from .util import bfh, bh2u, format_satoshis, json_decode +from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode from .import bitcoin from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .i18n import _ @@ -81,8 +81,8 @@ def command(s): wallet = args[0].wallet password = kwargs.get('password') if c.requires_wallet and wallet is None: - raise BaseException("wallet not loaded. Use 'electrum-zcash daemon load_wallet'") - if c.requires_password and password is None and wallet.storage.get('use_encryption'): + raise Exception("wallet not loaded. Use 'electrum-zcash daemon load_wallet'") + if c.requires_password and password is None and wallet.has_password(): return {'error': 'Password required' } return func(*args, **kwargs) return func_wrapper @@ -125,7 +125,7 @@ class Commands: @command('') def create(self): """Create a new wallet""" - raise BaseException('Not a JSON-RPC command') + raise Exception('Not a JSON-RPC command') @command('wn') def restore(self, text): @@ -133,11 +133,13 @@ class Commands: public key, a master private key, a list of Zcash addresses or Zcash private keys. If you want to be prompted for your seed, type '?' or ':' (concealed) """ - raise BaseException('Not a JSON-RPC command') + raise Exception('Not a JSON-RPC command') @command('wp') def password(self, password=None, new_password=None): """Change wallet password. """ + if self.wallet.storage.is_encrypted_with_hw_device() and new_password: + raise Exception("Can't change the password of a wallet encrypted with a hw device.") b = self.wallet.storage.is_encrypted() self.wallet.update_password(password, new_password, b) self.wallet.storage.write() @@ -148,34 +150,38 @@ class Commands: """Return a configuration variable. """ return self.config.get(key) + @classmethod + def _setconfig_normalize_value(cls, key, value): + if key not in ('rpcuser', 'rpcpassword'): + value = json_decode(value) + try: + value = ast.literal_eval(value) + except: + pass + return value + @command('') def setconfig(self, key, value): """Set a configuration variable. 'value' may be a string or a Python expression.""" - if key not in ('rpcuser', 'rpcpassword'): - value = json_decode(value) + value = self._setconfig_normalize_value(key, value) self.config.set_key(key, value) return True @command('') - def make_seed(self, nbits=132, entropy=1, language=None): + def make_seed(self, nbits=132, language=None): """Create a seed""" from .mnemonic import Mnemonic t = 'standard' - s = Mnemonic(language).make_seed(t, nbits, custom_entropy=entropy) + s = Mnemonic(language).make_seed(t, nbits) return s - @command('') - def check_seed(self, seed, entropy=1, language=None): - """Check that a seed was generated with given entropy""" - from .mnemonic import Mnemonic - return Mnemonic(language).check_seed(seed, entropy) - @command('n') def getaddresshistory(self, address): """Return the transaction history of any address. Note: This is a walletless server query, results are not checked by SPV. """ - return self.network.synchronous_get(('blockchain.address.get_history', [address])) + sh = bitcoin.address_to_scripthash(address) + return self.network.synchronous_get(('blockchain.scripthash.get_history', [sh])) @command('w') def listunspent(self): @@ -192,7 +198,8 @@ class Commands: """Returns the UTXO list of any address. Note: This is a walletless server query, results are not checked by SPV. """ - return self.network.synchronous_get(('blockchain.address.listunspent', [address])) + sh = bitcoin.address_to_scripthash(address) + return self.network.synchronous_get(('blockchain.scripthash.listunspent', [sh])) @command('') def serialize(self, jsontx): @@ -203,7 +210,7 @@ class Commands: keypairs = {} inputs = jsontx.get('inputs') outputs = jsontx.get('outputs') - locktime = jsontx.get('locktime', 0) + locktime = jsontx.get('lockTime', 0) for txin in inputs: if txin.get('output'): prevout_hash, prevout_n = txin['output'].split(':') @@ -314,20 +321,12 @@ class Commands: """Return the balance of any address. Note: This is a walletless server query, results are not checked by SPV. """ - out = self.network.synchronous_get(('blockchain.address.get_balance', [address])) + sh = bitcoin.address_to_scripthash(address) + out = self.network.synchronous_get(('blockchain.scripthash.get_balance', [sh])) out["confirmed"] = str(Decimal(out["confirmed"])/COIN) out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN) return out - @command('n') - def getproof(self, address): - """Get Merkle branch of an address in the UTXO set""" - p = self.network.synchronous_get(('blockchain.address.get_proof', [address])) - out = [] - for i,s in p: - out.append(i) - return out - @command('n') def getmerkle(self, txid, height): """Get Merkle branch of a transaction included in a block. Electrum-Zcash @@ -341,7 +340,7 @@ class Commands: @command('') def version(self): - """Return the version of electrum-zcash.""" + """Return the version of Electrum-Zcash.""" from .version import ELECTRUM_VERSION return ELECTRUM_VERSION @@ -378,7 +377,7 @@ class Commands: return None out = self.wallet.contacts.resolve(x) if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False: - raise BaseException('cannot verify alias', x) + raise Exception('cannot verify alias', x) return out['address'] @command('n') @@ -444,46 +443,20 @@ class Commands: return tx.as_dict() @command('w') - def history(self): + def history(self, year=None, show_addresses=False, show_fiat=False): """Wallet history. Returns the transaction history of your wallet.""" - balance = 0 - out = [] - for item in self.wallet.get_history(): - tx_hash, height, conf, timestamp, value, balance = item - if timestamp: - date = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] - else: - date = "----" - label = self.wallet.get_label(tx_hash) - tx = self.wallet.transactions.get(tx_hash) - tx.deserialize() - input_addresses = [] - output_addresses = [] - for x in tx.inputs(): - if x['type'] == 'coinbase': continue - addr = x.get('address') - if addr == None: continue - if addr == "(pubkey)": - prevout_hash = x.get('prevout_hash') - prevout_n = x.get('prevout_n') - _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n) - if _addr: - addr = _addr - input_addresses.append(addr) - for addr, v in tx.get_outputs(): - output_addresses.append(addr) - out.append({ - 'txid': tx_hash, - 'timestamp': timestamp, - 'date': date, - 'input_addresses': input_addresses, - 'output_addresses': output_addresses, - 'label': label, - 'value': str(Decimal(value)/COIN) if value is not None else None, - 'height': height, - 'confirmations': conf - }) - return out + kwargs = {'show_addresses': show_addresses} + if year: + import time + start_date = datetime.datetime(year, 1, 1) + end_date = datetime.datetime(year+1, 1, 1) + kwargs['from_timestamp'] = time.mktime(start_date.timetuple()) + kwargs['to_timestamp'] = time.mktime(end_date.timetuple()) + if show_fiat: + from .exchange_rate import FxThread + fx = FxThread(self.config, None) + kwargs['fx'] = fx + return json_encode(self.wallet.get_full_history(**kwargs)) @command('w') def setlabel(self, key, label): @@ -545,7 +518,7 @@ class Commands: if raw: tx = Transaction(raw) else: - raise BaseException("Unknown transaction") + raise Exception("Unknown transaction") return tx.as_dict() @command('') @@ -574,7 +547,7 @@ class Commands: """Return a payment request""" r = self.wallet.get_payment_request(key, self.config) if not r: - raise BaseException("Request not found") + raise Exception("Request not found") return self._format_request(r) #@command('w') @@ -612,7 +585,7 @@ class Commands: @command('w') def addrequest(self, amount, memo='', expiration=None, force=False): """Create a payment request, using the first unused address of the wallet. - The address will be condidered as used after this operation. + The address will be considered as used after this operation. If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet.""" addr = self.wallet.get_unused_address() if addr is None: @@ -627,12 +600,21 @@ class Commands: out = self.wallet.get_payment_request(addr, self.config) return self._format_request(out) + @command('w') + def addtransaction(self, tx): + """ Add a transaction to the wallet history """ + tx = Transaction(tx) + if not self.wallet.add_transaction(tx.txid(), tx): + return False + self.wallet.save_transactions() + return tx.txid() + @command('wp') def signrequest(self, address, password=None): "Sign payment request with an OpenAlias" alias = self.config.get('alias') if not alias: - raise BaseException('No alias in your configuration') + raise Exception('No alias in your configuration') alias_addr = self.wallet.contacts.resolve(alias)['address'] self.wallet.sign_payment_request(address, alias, alias_addr, password) @@ -649,18 +631,20 @@ class Commands: @command('n') def notify(self, address, URL): - """Watch an address. Everytime the address changes, a http POST is sent to the URL.""" + """Watch an address. Every time the address changes, a http POST is sent to the URL.""" def callback(x): import urllib.request headers = {'content-type':'application/json'} data = {'address':address, 'status':x.get('result')} + serialized_data = util.to_bytes(json.dumps(data)) try: - req = urllib.request.Request(URL, json.dumps(data), headers) + req = urllib.request.Request(URL, serialized_data, headers) response_stream = urllib.request.urlopen(req, timeout=5) util.print_error('Got Response for %s' % address) except BaseException as e: util.print_error(str(e)) - self.network.send([('blockchain.address.subscribe', [address])], callback) + h = self.network.addr_to_scripthash(address) + self.network.send([('blockchain.scripthash.subscribe', [h])], callback) return True @command('wn') @@ -668,6 +652,12 @@ class Commands: """ return wallet synchronization status """ return self.wallet.is_up_to_date() + @command('n') + def getfeerate(self): + """Return current optimal fee rate per kilobyte, according + to config settings (static/dynamic)""" + return self.config.fee_per_kb() + @command('') def help(self): # for the python console @@ -708,7 +698,6 @@ command_options = { 'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."), 'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"), 'nbits': (None, "Number of bits of entropy"), - 'entropy': (None, "Custom entropy"), 'language': ("-L", "Default language for wordlist"), 'privkey': (None, "Private key. Set to '?' to get a prompt."), 'unsigned': ("-u", "Do not sign transaction"), @@ -721,6 +710,9 @@ command_options = { 'pending': (None, "Show only pending requests."), 'expired': (None, "Show only expired requests."), 'paid': (None, "Show only paid requests."), + 'show_addresses': (None, "Show input and output addresses"), + 'show_fiat': (None, "Show fiat value of transactions"), + 'year': (None, "Show history for a given year"), } @@ -731,7 +723,7 @@ arg_types = { 'num': int, 'nbits': int, 'imax': int, - 'entropy': int, + 'year': int, 'tx': tx_from_str, 'pubkeys': json_loads, 'jsontx': json_loads, @@ -794,7 +786,7 @@ def subparser_call(self, parser, namespace, values, option_string=None): parser = self._name_parser_map[parser_name] except KeyError: tup = parser_name, ', '.join(self._name_parser_map) - msg = _('unknown parser %r (choices: %s)') % tup + msg = _('unknown parser {!r} (choices: {})').format(*tup) raise ArgumentError(self, msg) # parse all the remaining options into the namespace # store any unrecognized options on the object, so that the top @@ -808,7 +800,7 @@ argparse._SubParsersAction.__call__ = subparser_call def add_network_options(parser): - parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only") + parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=None, help="connect to one server only") parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)") parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http") @@ -819,6 +811,7 @@ def add_global_options(parser): group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") + group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest") def get_parser(): # create main parser diff --git a/lib/constants.py b/lib/constants.py new file mode 100644 index 00000000..49993dcf --- /dev/null +++ b/lib/constants.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2018 The Electrum developers +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# 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 os +import json + + +def read_json(filename, default): + path = os.path.join(os.path.dirname(__file__), filename) + try: + with open(path, 'r') as f: + r = json.loads(f.read()) + except: + r = default + return r + + +class BitcoinMainnet: + + TESTNET = False + WIF_PREFIX = 0x80 + ADDRTYPE_P2PKH = bytes.fromhex('1CB8') + ADDRTYPE_P2SH = bytes.fromhex('1CBD') + GENESIS = "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08" + DEFAULT_PORTS = {'t': '50021', 's': '50022'} + DEFAULT_SERVERS = read_json('servers.json', {}) + CHECKPOINTS = [] + + XPRV_HEADERS = { + 'standard': 0x0488ade4, # xprv + } + XPUB_HEADERS = { + 'standard': 0x0488b21e, # xpub + } + DRKV_HEADER = 0x02fe52f8 # drkv + DRKP_HEADER = 0x02fe52cc # drkp + + +class BitcoinTestnet: + + TESTNET = True + WIF_PREFIX = 0xEF + ADDRTYPE_P2PKH = bytes.fromhex('1D25') + ADDRTYPE_P2SH = bytes.fromhex('1CBA') + GENESIS = "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38" + DEFAULT_PORTS = {'t': '51021', 's': '51022'} + DEFAULT_SERVERS = read_json('servers_testnet.json', {}) + CHECKPOINTS = [] + + XPRV_HEADERS = { + 'standard': 0x04358394, # tprv + } + XPUB_HEADERS = { + 'standard': 0x043587cf, # tpub + } + DRKV_HEADER = 0x3a8061a0 # DRKV + DRKP_HEADER = 0x3a805837 # DRKP + + +class BitcoinRegtest(BitcoinTestnet): + + GENESIS = "029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327" + DEFAULT_SERVERS = read_json('servers_regtest.json', {}) + CHECKPOINTS = [] + + +# don't import net directly, import the module instead (so that net is singleton) +net = BitcoinMainnet + + +def set_mainnet(): + global net + net = BitcoinMainnet + + +def set_testnet(): + global net + net = BitcoinTestnet + + +def set_regtest(): + global net + net = BitcoinRegtest diff --git a/lib/contacts.py b/lib/contacts.py index 165d45b5..ad3f92c8 100644 --- a/lib/contacts.py +++ b/lib/contacts.py @@ -22,10 +22,14 @@ # SOFTWARE. import re import dns +from dns.exception import DNSException import json +import traceback +import sys from . import bitcoin from . import dnssec +from .util import export_meta, import_meta, print_error, to_string class Contacts(dict): @@ -48,14 +52,15 @@ class Contacts(dict): self.storage.put('contacts', dict(self)) def import_file(self, path): - try: - with open(path, 'r') as f: - d = self._validate(json.loads(f.read())) - except: - return - self.update(d) + import_meta(path, self._validate, self.load_meta) + + def load_meta(self, data): + self.update(data) self.save() + def export_file(self, filename): + export_meta(self, filename) + def __setitem__(self, key, value): dict.__setitem__(self, key, value) self.save() @@ -92,10 +97,14 @@ class Contacts(dict): def resolve_openalias(self, url): # support email-style addresses, per the OA standard url = url.replace('@', '.') - records, validated = dnssec.query(url, dns.rdatatype.TXT) + try: + records, validated = dnssec.query(url, dns.rdatatype.TXT) + except DNSException as e: + print_error('Error resolving openalias: ', str(e)) + return None prefix = 'zcash' for record in records: - string = record.strings[0] + string = to_string(record.strings[0], 'utf8') if string.startswith('oa1:' + prefix): address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)') name = self.find_regex(string, r'recipient_name=([^;]+)') @@ -113,13 +122,13 @@ class Contacts(dict): return None def _validate(self, data): - for k,v in list(data.items()): + for k, v in list(data.items()): if k == 'contacts': return self._validate(v) if not bitcoin.is_address(k): data.pop(k) else: - _type,_ = v + _type, _ = v if _type != 'address': data.pop(k) return data diff --git a/lib/currencies.json b/lib/currencies.json index ed6fa924..db6a5119 100644 --- a/lib/currencies.json +++ b/lib/currencies.json @@ -6,7 +6,6 @@ "BTC" ], "CoinMarketCap": [ - "BTC", "USD" ] } diff --git a/lib/daemon.py b/lib/daemon.py index 3199d69a..d66716ab 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -25,6 +25,8 @@ import ast import os import time +import traceback +import sys # from jsonrpc import JSONRPCResponseManager import jsonrpclib @@ -58,7 +60,7 @@ def get_fd_or_server(config): lockfile = get_lockfile(config) while True: try: - return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY), None + return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644), None except OSError: pass server = get_server(config) @@ -121,13 +123,12 @@ class Daemon(DaemonThread): self.config = config if config.get('offline'): self.network = None - self.fx = None else: self.network = Network(config) self.network.start() - self.fx = FxThread(config, self.network) + self.fx = FxThread(config, self.network) + if self.network: self.network.add_jobs([self.fx]) - self.gui = None self.wallets = {} # Setup JSONRPC server @@ -172,8 +173,9 @@ class Daemon(DaemonThread): elif sub == 'load_wallet': path = config.get_wallet_path() wallet = self.load_wallet(path, config.get('password')) - self.cmd_runner.wallet = wallet - response = True + if wallet is not None: + self.cmd_runner.wallet = wallet + response = wallet is not None elif sub == 'close_wallet': path = config.get_wallet_path() if path in self.wallets: @@ -184,6 +186,9 @@ class Daemon(DaemonThread): elif sub == 'status': if self.network: p = self.network.get_parameters() + current_wallet = self.cmd_runner.wallet + current_wallet_path = current_wallet.storage.path \ + if current_wallet else None response = { 'path': self.network.config.path, 'server': p[0], @@ -195,6 +200,7 @@ class Daemon(DaemonThread): 'version': ELECTRUM_VERSION, 'wallets': {k: w.is_up_to_date() for k, w in self.wallets.items()}, + 'current_wallet': current_wallet_path, 'fee_per_kb': self.config.fee_per_kb(), } else: @@ -301,4 +307,8 @@ class Daemon(DaemonThread): gui_name = 'qt' gui = __import__('electrum_zcash_gui.' + gui_name, fromlist=['electrum_zcash_gui']) self.gui = gui.ElectrumGui(config, self, plugins) - self.gui.main() + try: + self.gui.main() + except BaseException as e: + traceback.print_exc(file=sys.stdout) + # app will exit now diff --git a/lib/dnssec.py b/lib/dnssec.py index 15a0bc9e..6a8ac980 100644 --- a/lib/dnssec.py +++ b/lib/dnssec.py @@ -199,7 +199,7 @@ def check_query(ns, sub, _type, keys): elif answer[1].rdtype == dns.rdatatype.RRSIG: rrset, rrsig = answer else: - raise BaseException('No signature set in record') + raise Exception('No signature set in record') if keys is None: keys = {dns.name.from_text(sub):rrset} dns.dnssec.validate(rrset, rrsig, keys) @@ -248,7 +248,7 @@ def get_and_validate(ns, url, _type): continue break else: - raise BaseException("DS does not match DNSKEY") + raise Exception("DS does not match DNSKEY") # set key for next iteration keys = {name: rrset} # get TXT record (signed by zone) diff --git a/lib/exchange_rate.py b/lib/exchange_rate.py index f2cdcac1..19c554fa 100644 --- a/lib/exchange_rate.py +++ b/lib/exchange_rate.py @@ -2,6 +2,8 @@ from datetime import datetime import inspect import requests import sys +import os +import json from threading import Thread import time import csv @@ -39,7 +41,7 @@ class ExchangeBase(PrintError): def get_json(self, site, get_string): # APIs must have https url = ''.join(['https://', site, get_string]) - response = requests.request('GET', url, headers={'User-Agent' : 'Electrum-Zcash'}) + response = requests.request('GET', url, headers={'User-Agent' : 'Electrum-Zcash'}, timeout=10) return response.json() def get_csv(self, site, get_string): @@ -65,28 +67,54 @@ class ExchangeBase(PrintError): t.setDaemon(True) t.start() - def get_historical_rates_safe(self, ccy): + def read_historical_rates(self, ccy, cache_dir): + filename = os.path.join(cache_dir, self.name() + '_'+ ccy) + if os.path.exists(filename): + timestamp = os.stat(filename).st_mtime + try: + with open(filename, 'r', encoding='utf-8') as f: + h = json.loads(f.read()) + h['timestamp'] = timestamp + except: + h = None + else: + h = None + if h: + self.history[ccy] = h + self.on_history() + return h + + def get_historical_rates_safe(self, ccy, cache_dir): try: self.print_error("requesting fx history for", ccy) - self.history[ccy] = self.historical_rates(ccy) + h = self.request_history(ccy) self.print_error("received fx history for", ccy) - self.on_history() except BaseException as e: self.print_error("failed fx history:", e) + return + filename = os.path.join(cache_dir, self.name() + '_' + ccy) + with open(filename, 'w', encoding='utf-8') as f: + f.write(json.dumps(h)) + h['timestamp'] = time.time() + self.history[ccy] = h + self.on_history() - def get_historical_rates(self, ccy): - result = self.history.get(ccy) - if not result and ccy in self.history_ccys(): - t = Thread(target=self.get_historical_rates_safe, args=(ccy,)) + def get_historical_rates(self, ccy, cache_dir): + if ccy not in self.history_ccys(): + return + h = self.history.get(ccy) + if h is None: + h = self.read_historical_rates(ccy, cache_dir) + if h is None or h['timestamp'] < time.time() - 24*3600: + t = Thread(target=self.get_historical_rates_safe, args=(ccy, cache_dir)) t.setDaemon(True) t.start() - return result def history_ccys(self): return [] def historical_rate(self, ccy, d_t): - return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d')) + return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'), 'NaN') def get_currencies(self): rates = self.get_rates('') @@ -96,7 +124,7 @@ class ExchangeBase(PrintError): class Bittrex(ExchangeBase): def get_rates(self, ccy): json = self.get_json('bittrex.com', - '/api/v1.1/public/getticker?market=BTC-DASH') + '/api/v1.1/public/getticker?market=BTC-ZEC') quote_currencies = {} if not json.get('success', False): return quote_currencies @@ -109,21 +137,20 @@ class Poloniex(ExchangeBase): def get_rates(self, ccy): json = self.get_json('poloniex.com', '/public?command=returnTicker') quote_currencies = {} - dash_ticker = json.get('BTC_DASH') - quote_currencies['BTC'] = Decimal(dash_ticker['last']) + zcash_ticker = json.get('BTC_ZEC') + quote_currencies['BTC'] = Decimal(zcash_ticker['last']) return quote_currencies class CoinMarketCap(ExchangeBase): def get_rates(self, ccy): - json = self.get_json('api.coinmarketcap.com', '/v1/ticker/dash/') + json = self.get_json('api.coinmarketcap.com', '/v1/ticker/1437/') quote_currencies = {} if not isinstance(json, list): return quote_currencies json = json[0] for ccy, key in [ ('USD', 'price_usd'), - ('BTC', 'price_btc'), ]: quote_currencies[ccy] = Decimal(json[key]) return quote_currencies @@ -141,7 +168,7 @@ def get_exchanges_and_currencies(): import os, json path = os.path.join(os.path.dirname(__file__), 'currencies.json') try: - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: return json.loads(f.read()) except: pass @@ -154,9 +181,11 @@ def get_exchanges_and_currencies(): exchange = klass(None, None) try: d[name] = exchange.get_currencies() + print(name, "ok") except: + print(name, "error") continue - with open(path, 'w') as f: + with open(path, 'w', encoding='utf-8') as f: f.write(json.dumps(d, indent=4, sort_keys=True)) return d @@ -185,7 +214,10 @@ class FxThread(ThreadJob): self.history_used_spot = False self.ccy_combo = None self.hist_checkbox = None + self.cache_dir = os.path.join(config.path, 'cache') self.set_exchange(self.config_exchange()) + if not os.path.exists(self.cache_dir): + os.mkdir(self.cache_dir) def get_currencies(self, h): d = get_exchanges_by_ccy(h) @@ -208,7 +240,7 @@ class FxThread(ThreadJob): # This runs from the plugins thread which catches exceptions if self.is_enabled(): if self.timeout ==0 and self.show_history(): - self.exchange.get_historical_rates(self.ccy) + self.exchange.get_historical_rates(self.ccy, self.cache_dir) if self.timeout <= time.time(): self.timeout = time.time() + 150 self.exchange.update(self.ccy) @@ -225,6 +257,12 @@ class FxThread(ThreadJob): def set_history_config(self, b): self.config.set_key('history_rates', bool(b)) + def get_history_capital_gains_config(self): + return bool(self.config.get('history_rates_capital_gains', False)) + + def set_history_capital_gains_config(self, b): + self.config.set_key('history_rates_capital_gains', bool(b)) + def get_fiat_address_config(self): return bool(self.config.get('fiat_address')) @@ -256,45 +294,65 @@ class FxThread(ThreadJob): # A new exchange means new fx quotes, initially empty. Force # a quote refresh self.timeout = 0 + self.exchange.read_historical_rates(self.ccy, self.cache_dir) def on_quotes(self): - self.network.trigger_callback('on_quotes') + if self.network: + self.network.trigger_callback('on_quotes') def on_history(self): - self.network.trigger_callback('on_history') + if self.network: + self.network.trigger_callback('on_history') def exchange_rate(self): '''Returns None, or the exchange rate as a Decimal''' rate = self.exchange.quotes.get(self.ccy) - if rate: - return Decimal(rate) + if rate is None: + return Decimal('NaN') + return Decimal(rate) + + def format_amount(self, btc_balance): + rate = self.exchange_rate() + return '' if rate.is_nan() else "%s" % self.value_str(btc_balance, rate) def format_amount_and_units(self, btc_balance): rate = self.exchange_rate() - return '' if rate is None else "%s %s" % (self.value_str(btc_balance, rate), self.ccy) + return '' if rate.is_nan() else "%s %s" % (self.value_str(btc_balance, rate), self.ccy) def get_fiat_status_text(self, btc_balance, base_unit, decimal_point): rate = self.exchange_rate() - return _(" (No FX rate available)") if rate is None else " 1 %s~%s %s" % (base_unit, + return _(" (No FX rate available)") if rate.is_nan() else " 1 %s~%s %s" % (base_unit, self.value_str(COIN / (10**(8 - decimal_point)), rate), self.ccy) + def fiat_value(self, satoshis, rate): + return Decimal('NaN') if satoshis is None else Decimal(satoshis) / COIN * Decimal(rate) + def value_str(self, satoshis, rate): - if satoshis is None: # Can happen with incomplete history - return _("Unknown") - if rate: - value = Decimal(satoshis) / COIN * Decimal(rate) - return "%s" % (self.ccy_amount_str(value, True)) - return _("No data") + return self.format_fiat(self.fiat_value(satoshis, rate)) + + def format_fiat(self, value): + if value.is_nan(): + return _("No data") + return "%s" % (self.ccy_amount_str(value, True)) def history_rate(self, d_t): + if d_t is None: + return Decimal('NaN') rate = self.exchange.historical_rate(self.ccy, d_t) # Frequently there is no rate for today, until tomorrow :) # Use spot quotes in that case - if rate is None and (datetime.today().date() - d_t.date()).days <= 2: - rate = self.exchange.quotes.get(self.ccy) + if rate == 'NaN' and (datetime.today().date() - d_t.date()).days <= 2: + rate = self.exchange.quotes.get(self.ccy, 'NaN') self.history_used_spot = True - return rate + return Decimal(rate) def historical_value_str(self, satoshis, d_t): - rate = self.history_rate(d_t) - return self.value_str(satoshis, rate) + return self.format_fiat(self.historical_value(satoshis, d_t)) + + def historical_value(self, satoshis, d_t): + return self.fiat_value(satoshis, self.history_rate(d_t)) + + def timestamp_rate(self, timestamp): + from electrum_zcash.util import timestamp_to_datetime + date = timestamp_to_datetime(timestamp) + return self.history_rate(date) diff --git a/lib/interface.py b/lib/interface.py index ac1495fb..b357e087 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -43,7 +43,7 @@ from . import pem def Connection(server, queue, config_path): - """Makes asynchronous connections to a remote electrum server. + """Makes asynchronous connections to a remote Electrum server. Returns the running thread that is making the connection. Once the thread has connected, it finishes, placing a tuple on the @@ -144,7 +144,7 @@ class TcpConnection(threading.Thread, util.PrintError): context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_path) s = context.wrap_socket(s, do_handshake_on_connect=True) except ssl.SSLError as e: - print_error(e) + self.print_error(e) s = None except: return @@ -172,8 +172,10 @@ class TcpConnection(threading.Thread, util.PrintError): # workaround android bug cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert) temporary_path = cert_path + '.temp' - with open(temporary_path,"w") as f: + with open(temporary_path, "w", encoding='utf-8') as f: f.write(cert) + f.flush() + os.fsync(f.fileno()) else: is_new = False @@ -199,7 +201,7 @@ class TcpConnection(threading.Thread, util.PrintError): os.unlink(rej) os.rename(temporary_path, rej) else: - with open(cert_path) as f: + with open(cert_path, encoding='utf-8') as f: cert = f.read() try: b = pem.dePem(cert, 'CERTIFICATE') @@ -238,7 +240,7 @@ class TcpConnection(threading.Thread, util.PrintError): class Interface(util.PrintError): """The Interface class handles a socket connected to a single remote - electrum server. It's exposed API is: + Electrum server. Its exposed API is: - Member functions close(), fileno(), get_responses(), has_timed_out(), ping_required(), queue_request(), send_requests() @@ -295,8 +297,8 @@ class Interface(util.PrintError): wire_requests = self.unsent_requests[0:n] try: self.pipe.send_all([make_dict(*r) for r in wire_requests]) - except socket.error as e: - self.print_error("socket error:", e) + except BaseException as e: + self.print_error("pipe send error:", e) return False self.unsent_requests = self.unsent_requests[n:] for request in wire_requests: @@ -396,7 +398,7 @@ def test_certificates(): certs = os.listdir(mydir) for c in certs: p = os.path.join(mydir,c) - with open(p) as f: + with open(p, encoding='utf-8') as f: cert = f.read() check_cert(c, cert) diff --git a/lib/keystore.py b/lib/keystore.py index 9285ad14..bc437ae1 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -28,8 +28,9 @@ from unicodedata import normalize from . import bitcoin from .bitcoin import * - -from .util import PrintError, InvalidPassword, hfu +from . import constants +from .util import (PrintError, InvalidPassword, hfu, WalletFileException, + BitcoinException) from .mnemonic import Mnemonic, load_wordlist from .plugins import run_hook @@ -45,6 +46,10 @@ class KeyStore(PrintError): def can_import(self): return False + def may_have_password(self): + """Returns whether the keystore can be encrypted with a password.""" + raise NotImplementedError() + def get_tx_derivations(self, tx): keypairs = {} for txin in tx.inputs(): @@ -71,6 +76,8 @@ class KeyStore(PrintError): return False return bool(self.get_tx_derivations(tx)) + def ready_to_sign(self): + return not self.is_watching_only() class Software_KeyStore(KeyStore): @@ -116,9 +123,6 @@ class Imported_KeyStore(Software_KeyStore): def is_deterministic(self): return False - def can_change_password(self): - return True - def get_master_public_key(self): return None @@ -138,7 +142,14 @@ class Imported_KeyStore(Software_KeyStore): def import_privkey(self, sec, password): txin_type, privkey, compressed = deserialize_privkey(sec) pubkey = public_key_from_private_key(privkey, compressed) - self.keypairs[pubkey] = pw_encode(sec, password) + # re-serialize the key so the internal storage format is consistent + serialized_privkey = serialize_privkey( + privkey, compressed, txin_type, internal_use=True) + # NOTE: if the same pubkey is reused for multiple addresses (script types), + # there will only be one pubkey-privkey pair for it in self.keypairs, + # and the privkey will encode a txin_type but that txin_type cannot be trusted. + # Removing keys complicates this further. + self.keypairs[pubkey] = pw_encode(serialized_privkey, password) return txin_type, pubkey def delete_imported_key(self, key): @@ -196,9 +207,6 @@ class Deterministic_KeyStore(Software_KeyStore): def is_watching_only(self): return not self.has_seed() - def can_change_password(self): - return not self.is_watching_only() - def add_seed(self, seed): if self.seed: raise Exception("a seed exists") @@ -504,7 +512,7 @@ class Hardware_KeyStore(KeyStore, Xpub): } def unpaired(self): - '''A device paired with the wallet was diconnected. This can be + '''A device paired with the wallet was disconnected. This can be called in any thread context.''' self.print_error("unpaired") @@ -522,9 +530,24 @@ class Hardware_KeyStore(KeyStore, Xpub): assert not self.has_seed() return False - def can_change_password(self): - return False + def get_password_for_storage_encryption(self): + from .storage import get_derivation_used_for_hw_device_encryption + client = self.plugin.get_client(self) + derivation = get_derivation_used_for_hw_device_encryption() + xpub = client.get_xpub(derivation, "standard") + password = self.get_pubkey_from_xpub(xpub, ()) + return password + def has_usable_connection_with_device(self): + if not hasattr(self, 'plugin'): + return False + client = self.plugin.get_client(self, force_pair=False) + if client is None: + return False + return client.has_usable_connection_with_device() + + def ready_to_sign(self): + return super().ready_to_sign() and self.has_usable_connection_with_device() def bip39_normalize_passphrase(passphrase): @@ -571,10 +594,19 @@ def bip39_is_checksum_valid(mnemonic): def from_bip39_seed(seed, passphrase, derivation): k = BIP32_KeyStore({}) bip32_seed = bip39_to_seed(seed, passphrase) - t = 'standard' # bip43 - k.add_xprv_from_seed(bip32_seed, t, derivation) + xtype = xtype_from_derivation(derivation) + k.add_xprv_from_seed(bip32_seed, xtype, derivation) return k + +def xtype_from_derivation(derivation): + """Returns the script type to be used for this derivation.""" + if derivation.startswith("m/84'") or derivation.startswith("m/49'"): + raise Exception('Unknown bip43 derivation purpose %s' % derivation[:5]) + else: + return 'standard' + + # extended pubkeys def is_xpubkey(x_pubkey): @@ -599,7 +631,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 @@ -618,14 +651,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': @@ -635,7 +669,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 @@ -671,10 +706,9 @@ is_private_key = lambda x: is_xprv(x) or is_private_key_list(x) is_bip32_key = lambda x: is_xprv(x) or is_xpub(x) -def bip44_derivation(account_id): - bip = 44 - coin = 1 if bitcoin.NetworkConstants.TESTNET else 133 - return "m/%d'/%d'/%d'" % (bip, coin, int(account_id)) +def bip44_derivation(account_id, bip43_purpose=44): + coin = 1 if constants.net.TESTNET else 133 + return "m/%d'/%d'/%d'" % (bip43_purpose, coin, int(account_id)) def from_seed(seed, passphrase, is_p2sh): t = seed_type(seed) @@ -690,7 +724,7 @@ def from_seed(seed, passphrase, is_p2sh): xtype = 'standard' 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): @@ -724,5 +758,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/mnemonic.py b/lib/mnemonic.py index 7096e20f..17c2cafc 100644 --- a/lib/mnemonic.py +++ b/lib/mnemonic.py @@ -91,7 +91,7 @@ def normalize_text(seed): def load_wordlist(filename): path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: s = f.read().strip() s = unicodedata.normalize('NFKD', s) lines = s.split('\n') @@ -157,30 +157,24 @@ class Mnemonic(object): i = i*n + k return i - def check_seed(self, seed, custom_entropy): - assert is_new_seed(seed) - i = self.mnemonic_decode(seed) - return i % custom_entropy == 0 - - def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1): + def make_seed(self, seed_type='standard', num_bits=132): prefix = version.seed_prefix(seed_type) - # increase num_bits in order to obtain a uniform distibution for the last word + # increase num_bits in order to obtain a uniform distribution for the last word bpw = math.log(len(self.wordlist), 2) - num_bits = int(math.ceil(num_bits/bpw) * bpw) - # handle custom entropy; make sure we add at least 16 bits - n_custom = int(math.ceil(math.log(custom_entropy, 2))) - n = max(16, num_bits - n_custom) - print_error("make_seed", prefix, "adding %d bits"%n) - my_entropy = 1 - while my_entropy < pow(2, n - bpw): + # rounding + n = int(math.ceil(num_bits/bpw) * bpw) + print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n) + entropy = 1 + while entropy < pow(2, n - bpw): # try again if seed would not contain enough words - my_entropy = ecdsa.util.randrange(pow(2, n)) + entropy = ecdsa.util.randrange(pow(2, n)) nonce = 0 while True: nonce += 1 - i = custom_entropy * (my_entropy + nonce) + i = entropy + nonce seed = self.mnemonic_encode(i) - assert i == self.mnemonic_decode(seed) + if i != self.mnemonic_decode(seed): + raise Exception('Cannot extract same entropy from mnemonic!') if is_old_seed(seed): continue if is_new_seed(seed, prefix): diff --git a/lib/network.py b/lib/network.py index bb5c047a..10fbf2ee 100644 --- a/lib/network.py +++ b/lib/network.py @@ -1,4 +1,3 @@ - # Electrum - Lightweight Bitcoin Client # Copyright (c) 2011-2016 Thomas Voegtlin # @@ -38,10 +37,11 @@ import socks from . import util from . import bitcoin from .bitcoin import * -from .blockchain import HDR_LEN, CHUNK_LEN +from . import constants from .interface import Connection, Interface from . import blockchain from .version import ELECTRUM_VERSION, PROTOCOL_VERSION +from .i18n import _ NODES_RETRY_INTERVAL = 60 @@ -61,7 +61,7 @@ def parse_servers(result): for v in item[2]: if re.match("[st]\d*", v): protocol, port = v[0], v[1:] - if port == '': port = bitcoin.NetworkConstants.DEFAULT_PORTS[protocol] + if port == '': port = constants.net.DEFAULT_PORTS[protocol] out[protocol] = port elif re.match("v(.?)+", v): version = v[1:] @@ -95,7 +95,7 @@ def filter_protocol(hostmap, protocol = 's'): def pick_random_server(hostmap = None, protocol = 's', exclude_set = set()): if hostmap is None: - hostmap = bitcoin.NetworkConstants.DEFAULT_SERVERS + hostmap = constants.net.DEFAULT_SERVERS eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set) return random.choice(eligible) if eligible else None @@ -139,8 +139,9 @@ def deserialize_proxy(s): def deserialize_server(server_str): - host, port, protocol = str(server_str).split(':') - assert protocol in 'st' + host, port, protocol = str(server_str).rsplit(':', 2) + if protocol not in 'st': + raise ValueError('invalid network protocol: {}'.format(protocol)) int(port) # Throw if cannot be converted to int return host, port, protocol @@ -174,15 +175,16 @@ class Network(util.DaemonThread): if self.blockchain_index not in self.blockchains.keys(): self.blockchain_index = 0 # Server for addresses and transactions - self.default_server = self.config.get('server') + self.default_server = self.config.get('server', None) # Sanitize default server - try: - deserialize_server(self.default_server) - except: - self.default_server = None + if self.default_server: + try: + deserialize_server(self.default_server) + except: + self.print_error('Warning: failed to parse server-string; falling back to random.') + self.default_server = None if not self.default_server: self.default_server = pick_random_server() - self.lock = threading.Lock() self.pending_sends = [] self.message_id = 0 @@ -219,6 +221,7 @@ class Network(util.DaemonThread): self.interfaces = {} self.auto_connect = self.config.get('auto_connect', True) self.connecting = set() + self.requested_chunks = set() self.socket_queue = queue.Queue() self.start_network(deserialize_server(self.default_server)[2], deserialize_proxy(self.config.get('proxy'))) @@ -244,7 +247,7 @@ class Network(util.DaemonThread): return [] path = os.path.join(self.config.path, "recent_servers") try: - with open(path, "r") as f: + with open(path, "r", encoding='utf-8') as f: data = f.read() return json.loads(data) except: @@ -256,7 +259,7 @@ class Network(util.DaemonThread): path = os.path.join(self.config.path, "recent_servers") s = json.dumps(self.recent_servers, indent=4, sort_keys=True) try: - with open(path, "w") as f: + with open(path, "w", encoding='utf-8') as f: f.write(s) except: pass @@ -306,6 +309,9 @@ class Network(util.DaemonThread): # Resend unanswered requests requests = self.unanswered_requests.values() self.unanswered_requests = {} + if self.interface.ping_required(): + params = [ELECTRUM_VERSION, PROTOCOL_VERSION] + self.queue_request('server.version', params, self.interface) for request in requests: message_id = self.queue_request(request[0], request[1]) self.unanswered_requests[message_id] = request @@ -314,15 +320,14 @@ class Network(util.DaemonThread): self.queue_request('server.peers.subscribe', []) self.request_fee_estimates() self.queue_request('blockchain.relayfee', []) - if self.interface.ping_required(): - params = [ELECTRUM_VERSION, PROTOCOL_VERSION] - self.queue_request('server.version', params, self.interface) - for h in self.subscribed_addresses: + for h in list(self.subscribed_addresses): self.queue_request('blockchain.scripthash.subscribe', [h]) def request_fee_estimates(self): + from .simple_config import FEE_ETA_TARGETS self.config.requested_fee_estimates() - for i in bitcoin.FEE_TARGETS: + self.queue_request('mempool.get_fee_histogram', []) + for i in FEE_ETA_TARGETS: self.queue_request('blockchain.estimatefee', [i]) def get_status_value(self, key): @@ -332,6 +337,8 @@ class Network(util.DaemonThread): value = self.banner elif key == 'fee': value = self.config.fee_estimates + elif key == 'fee_histogram': + value = self.config.mempool_fees elif key == 'updated': value = (self.get_local_height(), self.get_server_height()) elif key == 'servers': @@ -359,7 +366,7 @@ class Network(util.DaemonThread): return list(self.interfaces.keys()) def get_servers(self): - out = bitcoin.NetworkConstants.DEFAULT_SERVERS + out = constants.net.DEFAULT_SERVERS if self.irc_servers: out.update(filter_version(self.irc_servers.copy())) else: @@ -543,6 +550,11 @@ class Network(util.DaemonThread): elif method == 'server.donation_address': if error is None: self.donation_address = result + elif method == 'mempool.get_fee_histogram': + if error is None: + self.print_error('fee_histogram', result) + self.config.mempool_fees = result + self.notify('fee_histogram') elif method == 'blockchain.estimatefee': if error is None and result > 0: i = params[0] @@ -552,14 +564,12 @@ class Network(util.DaemonThread): self.notify('fee') elif method == 'blockchain.relayfee': if error is None: - self.relay_fee = int(result * COIN) + self.relay_fee = int(result * COIN) if result is not None else None self.print_error("relayfee", self.relay_fee) - elif method == 'blockchain.block.headers': - height, count = params - if count == 1: - self.on_get_header(interface, response, height) - elif count == CHUNK_LEN: - self.on_get_chunk(interface, response, height) + elif method == 'blockchain.block.get_chunk': + self.on_get_chunk(interface, response) + elif method == 'blockchain.block.get_header': + self.on_get_header(interface, response) for callback in callbacks: callback(response) @@ -668,7 +678,7 @@ class Network(util.DaemonThread): # check cached response for subscriptions r = self.sub_cache.get(k) if r is not None: - util.print_error("cache hit", k) + self.print_error("cache hit", k) callback(r) else: message_id = self.queue_request(method, params) @@ -707,7 +717,7 @@ class Network(util.DaemonThread): interface.mode = 'default' interface.request = None self.interfaces[server] = interface - self.queue_request('blockchain.headers.subscribe', [True], interface) + self.queue_request('blockchain.headers.subscribe', [], interface) if server == self.default_server: self.switch_to_interface(server) #self.notify('interfaces') @@ -758,77 +768,77 @@ class Network(util.DaemonThread): if self.config.is_fee_estimates_update_required(): self.request_fee_estimates() - def request_chunk(self, interface, idx): - interface.print_error("requesting chunk %d" % idx) - self.queue_request('blockchain.block.headers', - [CHUNK_LEN*idx, CHUNK_LEN], interface) - interface.request = idx - interface.req_time = time.time() + def request_chunk(self, interface, index): + if index in self.requested_chunks: + return + interface.print_error("requesting chunk %d" % index) + self.requested_chunks.add(index) + self.queue_request('blockchain.block.get_chunk', [index], interface) - def on_get_chunk(self, interface, response, height): + def on_get_chunk(self, interface, response): '''Handle receiving a chunk of block headers''' error = response.get('error') result = response.get('result') - if result is None or error is not None: + params = response.get('params') + blockchain = interface.blockchain + if result is None or params is None or error is not None: interface.print_error(error or 'bad response') return - - hex_chunk = result.get('hex', None) + index = params[0] # Ignore unsolicited chunks - index = height // CHUNK_LEN - if interface.request != index or height / CHUNK_LEN != index: + if index not in self.requested_chunks: + interface.print_error("received chunk %d (unsolicited)" % index) return - - connect = interface.blockchain.connect_chunk(index, hex_chunk) - # If not finished, get the next chunk + else: + interface.print_error("received chunk %d" % index) + self.requested_chunks.remove(index) + connect = blockchain.connect_chunk(index, result) if not connect: self.connection_down(interface.server) return - if interface.blockchain.height() < interface.tip: + # If not finished, get the next chunk + if index >= len(blockchain.checkpoints) and blockchain.height() < interface.tip: self.request_chunk(interface, index+1) else: - interface.request = None interface.mode = 'default' - interface.print_error('catch up done', interface.blockchain.height()) - interface.blockchain.catch_up = None + interface.print_error('catch up done', blockchain.height()) + blockchain.catch_up = None self.notify('updated') def request_header(self, interface, height): #interface.print_error("requesting header %d" % height) - self.queue_request('blockchain.block.headers', [height, 1], interface) + self.queue_request('blockchain.block.get_header', [height], interface) interface.request = height interface.req_time = time.time() - def on_get_header(self, interface, response, height): + def on_get_header(self, interface, response): '''Handle receiving a single block header''' - result = response.get('result', {}) - hex_header = result.get('hex', None) - + header = response.get('result') + if not header: + interface.print_error(response) + self.connection_down(interface.server) + return + height = header.get('block_height') if interface.request != height: interface.print_error("unsolicited header",interface.request, height) self.connection_down(interface.server) return - - if not hex_header: - interface.print_error(response) - self.connection_down(interface.server) - return - - if len(hex_header) != HDR_LEN*2: - interface.print_error('wrong header length', interface.request) - self.connection_down(interface.server) - return - - header = blockchain.deserialize_header(bfh(hex_header), height) - chain = blockchain.check_header(header) if interface.mode == 'backward': - if chain: + can_connect = blockchain.can_connect(header) + if can_connect and can_connect.catch_up is None: + interface.mode = 'catch_up' + interface.blockchain = can_connect + interface.blockchain.save_header(header) + next_height = height + 1 + interface.blockchain.catch_up = interface.server + elif chain: interface.print_error("binary search") interface.mode = 'binary' interface.blockchain = chain interface.good = height next_height = (interface.bad + interface.good) // 2 + assert next_height >= self.max_checkpoint(), (interface.bad, interface.good) else: if height == 0: self.connection_down(interface.server) @@ -837,7 +847,7 @@ class Network(util.DaemonThread): interface.bad = height interface.bad_header = header delta = interface.tip - height - next_height = max(0, interface.tip - 2 * delta) + next_height = max(self.max_checkpoint(), interface.tip - 2 * delta) elif interface.mode == 'binary': if chain: @@ -848,6 +858,7 @@ class Network(util.DaemonThread): interface.bad_header = header if interface.bad != interface.good + 1: next_height = (interface.bad + interface.good) // 2 + assert next_height >= self.max_checkpoint() elif not interface.blockchain.can_connect(interface.bad_header, check_height=False): self.connection_down(interface.server) next_height = None @@ -912,11 +923,11 @@ class Network(util.DaemonThread): self.notify('updated') else: - raise BaseException(interface.mode) + raise Exception(interface.mode) # If not finished, get the next header if next_height: if interface.mode == 'catch_up' and interface.tip > next_height + 50: - self.request_chunk(interface, next_height // CHUNK_LEN) + self.request_chunk(interface, next_height // 2016) else: self.request_header(interface, next_height) else: @@ -957,33 +968,18 @@ class Network(util.DaemonThread): def init_headers_file(self): b = self.blockchains[0] - if b.get_hash(0) == bitcoin.NetworkConstants.GENESIS: - self.downloading_headers = False - return filename = b.path() - def download_thread(): - try: - import urllib.request, socket - socket.setdefaulttimeout(30) - self.print_error("downloading ", bitcoin.NetworkConstants.HEADERS_URL) - urllib.request.urlretrieve(bitcoin.NetworkConstants.HEADERS_URL, filename + '.tmp') - os.rename(filename + '.tmp', filename) - self.print_error("done.") - except Exception: - self.print_error("download failed. creating file", filename) - open(filename, 'wb+').close() - b = self.blockchains[0] - with b.lock: b.update_size() - self.downloading_headers = False - self.downloading_headers = True - t = threading.Thread(target = download_thread) - t.daemon = True - t.start() + length = 80 * len(constants.net.CHECKPOINTS) * 2016 + if not os.path.exists(filename) or os.path.getsize(filename) < length: + with open(filename, 'wb') as f: + if length>0: + f.seek(length-1) + f.write(b'\x00') + with b.lock: + b.update_size() def run(self): self.init_headers_file() - while self.is_running() and self.downloading_headers: - time.sleep(1) while self.is_running(): self.maintain_sockets() self.wait_on_sockets() @@ -994,17 +990,12 @@ class Network(util.DaemonThread): self.on_stop() def on_notify_header(self, interface, header): - height = header.get('height') - hex_header = header.get('hex') - if not height or not hex_header: + height = header.get('block_height') + if not height: return - - if len(hex_header) != HDR_LEN*2: - interface.print_error('wrong header length', interface.request) + if height < self.max_checkpoint(): self.connection_down(interface.server) return - - header = blockchain.deserialize_header(bfh(hex_header), height) interface.tip_header = header interface.tip = height if interface.mode != 'default': @@ -1029,14 +1020,17 @@ class Network(util.DaemonThread): interface.mode = 'backward' interface.bad = height interface.bad_header = header - self.request_header(interface, min(tip, height - 1)) + self.request_header(interface, min(tip +1, height - 1)) else: chain = self.blockchains[0] if chain.catch_up is None: chain.catch_up = interface interface.mode = 'catch_up' interface.blockchain = chain + self.print_error("switching to catchup mode", tip, self.blockchains) self.request_header(interface, 0) + else: + self.print_error("chain already catching up with", chain.catch_up.server) def blockchain(self): if self.interface and self.interface.blockchain is not None: @@ -1061,7 +1055,7 @@ class Network(util.DaemonThread): self.switch_to_interface(i.server) break else: - raise BaseException('blockchain not found', index) + raise Exception('blockchain not found', index) if self.interface: server = self.interface.server @@ -1078,9 +1072,9 @@ class Network(util.DaemonThread): try: r = q.get(True, timeout) except queue.Empty: - raise BaseException('Server did not answer') + raise util.TimeoutException(_('Server did not answer')) if r.get('error'): - raise BaseException(r.get('error')) + raise Exception(r.get('error')) return r.get('result') def broadcast(self, tx, timeout=30): @@ -1092,3 +1086,12 @@ class Network(util.DaemonThread): if out != tx_hash: return False, "error: " + out return True, out + + def export_checkpoints(self, path): + # run manually from the console to generate checkpoints + cp = self.blockchain().get_checkpoints() + with open(path, 'w', encoding='utf-8') as f: + f.write(json.dumps(cp, indent=4)) + + def max_checkpoint(self): + return max(0, len(constants.net.CHECKPOINTS) * 2016 - 1) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index a79141cb..acd66379 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -40,6 +40,7 @@ except ImportError: from . import bitcoin from . import util from .util import print_error, bh2u, bfh +from .util import export_meta, import_meta from . import transaction from . import x509 from . import rsakey @@ -88,13 +89,13 @@ def get_payment_request(url): error = "payment URL not pointing to a valid server" elif u.scheme == 'file': try: - with open(u.path, 'r') as f: + with open(u.path, 'r', encoding='utf-8') as f: data = f.read() except IOError: data = None error = "payment URL not pointing to a valid file" else: - raise BaseException("unknown scheme", url) + raise Exception("unknown scheme", url) pr = PaymentRequest(data, error) return pr @@ -147,7 +148,7 @@ class PaymentRequest: self.error = "Error: Cannot parse payment request" return False if not pr.signature: - # the address will be dispayed as requestor + # the address will be displayed as requestor self.requestor = None return True if pr.pki_type in ["x509+sha256", "x509+sha1"]: @@ -339,9 +340,9 @@ def verify_cert_chain(chain): x.check_date() else: if not x.check_ca(): - raise BaseException("ERROR: Supplied CA Certificate Error") + raise Exception("ERROR: Supplied CA Certificate Error") if not cert_num > 1: - raise BaseException("ERROR: CA Certificate Chain Not Provided by Payment Processor") + raise Exception("ERROR: CA Certificate Chain Not Provided by Payment Processor") # if the root CA is not supplied, add it to the chain ca = x509_chain[cert_num-1] if ca.getFingerprint() not in ca_list: @@ -351,7 +352,7 @@ def verify_cert_chain(chain): root = ca_list[f] x509_chain.append(root) else: - raise BaseException("Supplied CA Not Found in Trusted CA Store.") + raise Exception("Supplied CA Not Found in Trusted CA Store.") # verify the chain of signatures cert_num = len(x509_chain) for i in range(1, cert_num): @@ -372,10 +373,10 @@ def verify_cert_chain(chain): hashBytes = bytearray(hashlib.sha512(data).digest()) verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes) else: - raise BaseException("Algorithm not supported") + raise Exception("Algorithm not supported") util.print_error(self.error, algo.getComponentByName('algorithm')) if not verify: - raise BaseException("Certificate not Signed by Provided CA Certificate Chain") + raise Exception("Certificate not Signed by Provided CA Certificate Chain") return x509_chain[0], ca @@ -384,9 +385,9 @@ def check_ssl_config(config): from . import pem key_path = config.get('ssl_privkey') cert_path = config.get('ssl_chain') - with open(key_path, 'r') as f: + with open(key_path, 'r', encoding='utf-8') as f: params = pem.parse_private_key(f.read()) - with open(cert_path, 'r') as f: + with open(cert_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") # verify chain @@ -404,10 +405,10 @@ def check_ssl_config(config): def sign_request_with_x509(pr, key_path, cert_path): from . import pem - with open(key_path, 'r') as f: + with open(key_path, 'r', encoding='utf-8') as f: params = pem.parse_private_key(f.read()) privkey = rsakey.RSAKey(*params) - with open(cert_path, 'r') as f: + with open(cert_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") certificates = pb2.X509Certificates() @@ -452,7 +453,11 @@ class InvoiceStore(object): def set_paid(self, pr, txid): pr.tx = txid - self.paid[txid] = pr.get_id() + pr_id = pr.get_id() + self.paid[txid] = pr_id + if pr_id not in self.invoices: + # in case the user had deleted it previously + self.add(pr) def load(self, d): for k, v in d.items(): @@ -467,24 +472,29 @@ class InvoiceStore(object): continue def import_file(self, path): - try: - with open(path, 'r') as f: - d = json.loads(f.read()) - self.load(d) - except: - traceback.print_exc(file=sys.stderr) - return + def validate(data): + return data # TODO + import_meta(path, validate, self.on_import) + + def on_import(self, data): + self.load(data) self.save() - def save(self): - l = {} + def export_file(self, filename): + export_meta(self.dump(), filename) + + def dump(self): + d = {} for k, pr in self.invoices.items(): - l[k] = { + d[k] = { 'hex': bh2u(pr.raw), 'requestor': pr.requestor, 'txid': pr.tx } - self.storage.put('invoices', l) + return d + + def save(self): + self.storage.put('invoices', self.dump()) def get_status(self, key): pr = self.get(key) diff --git a/lib/plot.py b/lib/plot.py index 08f4301b..e5cb3c45 100644 --- a/lib/plot.py +++ b/lib/plot.py @@ -1,30 +1,32 @@ -from PyQt5.QtGui import * -from electrum.i18n import _ - - import datetime from collections import defaultdict -from electrum.bitcoin import COIN import matplotlib matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt import matplotlib.dates as md -from matplotlib.patches import Ellipse -from matplotlib.offsetbox import AnchoredOffsetbox, TextArea, DrawingArea, HPacker + +from .i18n import _ +from .bitcoin import COIN -def plot_history(wallet, history): +class NothingToPlotException(Exception): + def __str__(self): + return _("Nothing to plot.") + + +def plot_history(history): + if len(history) == 0: + raise NothingToPlotException() hist_in = defaultdict(int) hist_out = defaultdict(int) for item in history: - tx_hash, height, confirmations, timestamp, value, balance = item - if not confirmations: + if not item['confirmations']: continue - if timestamp is None: + if item['timestamp'] is None: continue - value = value*1./COIN - date = datetime.datetime.fromtimestamp(timestamp) + value = item['value'].value/COIN + date = item['date'] datenum = int(md.date2num(datetime.date(date.year, date.month, 1))) if value > 0: hist_in[datenum] += value @@ -43,10 +45,19 @@ def plot_history(wallet, history): xfmt = md.DateFormatter('%Y-%m') ax.xaxis.set_major_formatter(xfmt) width = 20 - dates, values = zip(*sorted(hist_in.items())) - r1 = axarr[0].bar(dates, values, width, label='incoming') - axarr[0].legend(loc='upper left') - dates, values = zip(*sorted(hist_out.items())) - r2 = axarr[1].bar(dates, values, width, color='r', label='outgoing') - axarr[1].legend(loc='upper left') + + r1 = None + r2 = None + dates_values = list(zip(*sorted(hist_in.items()))) + if dates_values and len(dates_values) == 2: + dates, values = dates_values + r1 = axarr[0].bar(dates, values, width, label='incoming') + axarr[0].legend(loc='upper left') + dates_values = list(zip(*sorted(hist_out.items()))) + if dates_values and len(dates_values) == 2: + dates, values = dates_values + r2 = axarr[1].bar(dates, values, width, color='r', label='outgoing') + axarr[1].legend(loc='upper left') + if r1 is None and r2 is None: + raise NothingToPlotException() return plt diff --git a/lib/plugins.py b/lib/plugins.py index 24494492..3c2be0f3 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -312,6 +312,8 @@ class DeviceMgr(ThreadJob, PrintError): # What we recognise. Each entry is a (vendor_id, product_id) # pair. self.recognised_hardware = set() + # Custom enumerate functions for devices we don't know about. + self.enumerate_func = set() # For synchronization self.lock = threading.RLock() self.hid_lock = threading.RLock() @@ -334,6 +336,9 @@ class DeviceMgr(ThreadJob, PrintError): for pair in device_pairs: self.recognised_hardware.add(pair) + def register_enumerate_func(self, func): + self.enumerate_func.add(func) + def create_client(self, device, handler, plugin): # Get from cache first client = self.client_lookup(device.id_) @@ -362,15 +367,20 @@ class DeviceMgr(ThreadJob, PrintError): if not xpub in self.xpub_ids: return _id = self.xpub_ids.pop(xpub) - client = self.client_lookup(_id) - self.clients.pop(client, None) - if client: - client.close() + self._close_client(_id) def unpair_id(self, id_): xpub = self.xpub_by_id(id_) if xpub: self.unpair_xpub(xpub) + else: + self._close_client(id_) + + def _close_client(self, id_): + client = self.client_lookup(id_) + self.clients.pop(client, None) + if client: + client.close() def pair_xpub(self, xpub, id_): with self.lock: @@ -393,7 +403,7 @@ class DeviceMgr(ThreadJob, PrintError): def client_for_keystore(self, plugin, handler, keystore, force_pair): self.print_error("getting client for keystore") if handler is None: - raise BaseException(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing.")) + raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing.")) handler.update_status(False) devices = self.scan_devices() xpub = keystore.xpub @@ -442,21 +452,23 @@ class DeviceMgr(ThreadJob, PrintError): # The user input has wrong PIN or passphrase, or cancelled input, # or it is not pairable raise DeviceUnpairableError( - _('Electrum-Zcash cannot pair with your %s.\n\n' + _('Electrum-Zcash cannot pair with your {}.\n\n' 'Before you request Zcash coins to be sent to addresses in this ' 'wallet, ensure you can pair with your device, or that you have ' 'its seed (and passphrase, if any). Otherwise all coins you ' - 'receive will be unspendable.') % plugin.device) + 'receive will be unspendable.').format(plugin.device)) def unpaired_device_infos(self, handler, plugin, devices=None): '''Returns a list of DeviceInfo objects: one for each connected, unpaired device accepted by the plugin.''' + if not plugin.libraries_available: + raise Exception('Missing libraries for {}'.format(plugin.name)) if devices is None: devices = self.scan_devices() devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)] infos = [] for device in devices: - if not device.product_key in plugin.DEVICE_IDS: + if device.product_key not in plugin.DEVICE_IDS: continue client = self.create_client(device, handler, plugin) if not client: @@ -472,9 +484,14 @@ class DeviceMgr(ThreadJob, PrintError): infos = self.unpaired_device_infos(handler, plugin, devices) if infos: break - msg = _('Please insert your %s. Verify the cable is ' - 'connected and that no other application is using it.\n\n' - 'Try to connect again?') % plugin.device + msg = _('Please insert your {}').format(plugin.device) + if keystore.label: + msg += ' ({})'.format(keystore.label) + msg += '. {}\n\n{}'.format( + _('Verify the cable is connected and that ' + 'no other application is using it.'), + _('Try to connect again?') + ) if not handler.yes_no_question(msg): raise UserCancelled() devices = None @@ -484,27 +501,27 @@ class DeviceMgr(ThreadJob, PrintError): for info in infos: if info.label == keystore.label: return info - msg = _("Please select which %s device to use:") % plugin.device - descriptions = [info.label + ' (%s)'%(_("initialized") if info.initialized else _("wiped")) for info in infos] + msg = _("Please select which {} device to use:").format(plugin.device) + descriptions = [str(info.label) + ' (%s)'%(_("initialized") if info.initialized else _("wiped")) for info in infos] c = handler.query_choice(msg, descriptions) if c is None: raise UserCancelled() info = infos[c] # save new label keystore.set_label(info.label) - handler.win.wallet.save_keystore() + if handler.win.wallet is not None: + handler.win.wallet.save_keystore() return info - def scan_devices(self): - # All currently supported hardware libraries use hid, so we - # assume it here. This can be easily abstracted if necessary. - # Note this import must be local so those without hardware - # wallet libraries are not affected. - import hid - self.print_error("scanning devices...") + def _scan_devices_with_hid(self): + try: + import hid + except ImportError: + return [] + with self.hid_lock: hid_list = hid.enumerate(0, 0) - # First see what's connected that we know about + devices = [] for d in hid_list: product_key = (d['vendor_id'], d['product_id']) @@ -518,14 +535,31 @@ class DeviceMgr(ThreadJob, PrintError): id_ += str(interface_number) + str(usage_page) devices.append(Device(d['path'], interface_number, id_, product_key, usage_page)) + return devices - # Now find out what was disconnected + def scan_devices(self): + self.print_error("scanning devices...") + + # First see what's connected that we know about + devices = self._scan_devices_with_hid() + + # Let plugin handlers enumerate devices we don't know about + for f in self.enumerate_func: + try: + new_devices = f() + except BaseException as e: + self.print_error('custom device enum failed. func {}, error {}' + .format(str(f), str(e))) + else: + devices.extend(new_devices) + + # find out what was disconnected pairs = [(dev.path, dev.id_) for dev in devices] disconnected_ids = [] with self.lock: connected = {} for client, pair in self.clients.items(): - if pair in pairs: + if pair in pairs and client.has_usable_connection_with_device(): connected[client] = pair else: disconnected_ids.append(pair[1]) diff --git a/lib/qrscanner.py b/lib/qrscanner.py index 7516d55d..56e0a18a 100644 --- a/lib/qrscanner.py +++ b/lib/qrscanner.py @@ -29,18 +29,18 @@ import ctypes if sys.platform == 'darwin': name = 'libzbar.dylib' -elif sys.platform == 'windows': - name = 'libzbar.dll' +elif sys.platform in ('windows', 'win32'): + name = 'libzbar-0.dll' else: name = 'libzbar.so.0' try: libzbar = ctypes.cdll.LoadLibrary(name) -except OSError: +except BaseException: libzbar = None -def scan_barcode(device='', timeout=-1, display=True, threaded=False): +def scan_barcode(device='', timeout=-1, display=True, threaded=False, try_again=True): if libzbar is None: raise RuntimeError("Cannot start QR scanner; zbar not available.") libzbar.zbar_symbol_get_data.restype = ctypes.c_char_p @@ -50,6 +50,10 @@ def scan_barcode(device='', timeout=-1, display=True, threaded=False): proc = libzbar.zbar_processor_create(threaded) libzbar.zbar_processor_request_size(proc, 640, 480) if libzbar.zbar_processor_init(proc, device.encode('utf-8'), display) != 0: + if try_again: + # workaround for a bug in "ZBar for Windows" + # libzbar.zbar_processor_init always seem to fail the first time around + return scan_barcode(device, timeout, display, threaded, try_again=False) raise RuntimeError("Can not start QR scanner; initialization failed.") libzbar.zbar_processor_set_visible(proc) if libzbar.zbar_process_one(proc, timeout): diff --git a/lib/servers_regtest.json b/lib/servers_regtest.json new file mode 100644 index 00000000..3e6cc52c --- /dev/null +++ b/lib/servers_regtest.json @@ -0,0 +1,8 @@ +{ + "127.0.0.1": { + "pruning": "-", + "s": "51022", + "t": "51021", + "version": "1.2" + } +} diff --git a/lib/servers_testnet.json b/lib/servers_testnet.json index cc07f7d4..3e6cc52c 100644 --- a/lib/servers_testnet.json +++ b/lib/servers_testnet.json @@ -1,5 +1,5 @@ { - "localhost": { + "127.0.0.1": { "pruning": "-", "s": "51022", "t": "51021", diff --git a/lib/simple_config.py b/lib/simple_config.py index 70a79d23..29ffa9ab 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -5,11 +5,21 @@ import os import stat from copy import deepcopy -from .util import user_dir, print_error, print_stderr, PrintError -from .bitcoin import MAX_FEE_RATE, FEE_TARGETS +from .util import (user_dir, print_error, PrintError, + NoDynamicFeeEstimates, format_satoshis) +from .i18n import _ + +FEE_ETA_TARGETS = [25, 10, 5, 2] +FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000] + +# satoshi per kbyte +FEERATE_MAX_DYNAMIC = 150000 +FEERATE_WARNING_HIGH_FEE = 100000 +FEERATE_FALLBACK_STATIC_FEE = 1000 +FEERATE_DEFAULT_RELAY = 1000 +FEERATE_STATIC_VALUES = [150, 300, 500, 1000, 1500, 2500, 3500, 5000, 7500, 10000] -SYSTEM_CONFIG_PATH = "/etc/electrum-zcash.conf" config = None @@ -24,35 +34,37 @@ def set_config(c): config = c +FINAL_CONFIG_VERSION = 2 + + class SimpleConfig(PrintError): """ The SimpleConfig class is responsible for handling operations involving configuration files. - There are 3 different sources of possible configuration values: + There are two different sources of possible configuration values: 1. Command line options. 2. User configuration (in the user's config directory) - 3. System configuration (in /etc/) - They are taken in order (1. overrides config options set in 2., that - override config set in 3.) + They are taken in order (1. overrides config options set in 2.) """ - fee_rates = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000] - def __init__(self, options={}, read_system_config_function=None, - read_user_config_function=None, read_user_dir_function=None): + def __init__(self, options=None, read_user_config_function=None, + read_user_dir_function=None): + + if options is None: + options = {} # This lock needs to be acquired for updating and reading the config in # a thread-safe way. self.lock = threading.RLock() + self.mempool_fees = {} self.fee_estimates = {} self.fee_estimates_last_updated = {} self.last_time_fee_estimates_requested = 0 # zero ensures immediate fees # The following two functions are there for dependency injection when # testing. - if read_system_config_function is None: - read_system_config_function = read_system_config if read_user_config_function is None: read_user_config_function = read_user_config if read_user_dir_function is None: @@ -62,90 +74,154 @@ class SimpleConfig(PrintError): # The command line options self.cmdline_options = deepcopy(options) - - # Portable wallets don't use a system config - if self.cmdline_options.get('portable', False): - self.system_config = {} - else: - self.system_config = read_system_config_function() + # don't allow to be set on CLI: + self.cmdline_options.pop('config_version', None) # Set self.path and read the user config self.user_config = {} # for self.get in electrum_path() self.path = self.electrum_path() self.user_config = read_user_config_function(self.path) - # Upgrade obsolete keys - self.fixup_keys({'auto_cycle': 'auto_connect'}) + if not self.user_config: + # avoid new config getting upgraded + self.user_config = {'config_version': FINAL_CONFIG_VERSION} + + # config "upgrade" - CLI options + self.rename_config_keys( + self.cmdline_options, {'auto_cycle': 'auto_connect'}, True) + + # config upgrade - user config + if self.requires_upgrade(): + self.upgrade() + # Make a singleton instance of 'self' set_config(self) def electrum_path(self): - # Read electrum_path from command line / system configuration + # Read electrum_path from command line # Otherwise use the user's default data directory. path = self.get('electrum_path') if path is None: path = self.user_dir() + def make_dir(path): + # Make directory if it does not yet exist. + if not os.path.exists(path): + if os.path.islink(path): + raise Exception('Dangling link: ' + path) + os.mkdir(path) + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + + make_dir(path) if self.get('testnet'): path = os.path.join(path, 'testnet') - - # Make directory if it does not yet exist. - if not os.path.exists(path): - if os.path.islink(path): - raise BaseException('Dangling link: ' + path) - os.makedirs(path) - os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + make_dir(path) + elif self.get('regtest'): + path = os.path.join(path, 'regtest') + make_dir(path) self.print_error("electrum-zcash directory", path) return path - def fixup_config_keys(self, config, keypairs): + def rename_config_keys(self, config, keypairs, deprecation_warning=False): + """Migrate old key names to new ones""" updated = False for old_key, new_key in keypairs.items(): if old_key in config: - if not new_key in config: + if new_key not in config: config[new_key] = config[old_key] + if deprecation_warning: + self.print_stderr('Note that the {} variable has been deprecated. ' + 'You should use {} instead.'.format(old_key, new_key)) del config[old_key] updated = True return updated - def fixup_keys(self, keypairs): - '''Migrate old key names to new ones''' - self.fixup_config_keys(self.cmdline_options, keypairs) - self.fixup_config_keys(self.system_config, keypairs) - if self.fixup_config_keys(self.user_config, keypairs): - self.save_user_config() - - def set_key(self, key, value, save = True): + def set_key(self, key, value, save=True): if not self.is_modifiable(key): - print_stderr("Warning: not changing config key '%s' set on the command line" % key) + self.print_stderr("Warning: not changing config key '%s' set on the command line" % key) return + self._set_key_in_user_config(key, value, save) + def _set_key_in_user_config(self, key, value, save=True): with self.lock: - self.user_config[key] = value + if value is not None: + self.user_config[key] = value + else: + self.user_config.pop(key, None) if save: self.save_user_config() - return def get(self, key, default=None): with self.lock: out = self.cmdline_options.get(key) if out is None: - out = self.user_config.get(key) - if out is None: - out = self.system_config.get(key, default) + out = self.user_config.get(key, default) return out + def requires_upgrade(self): + return self.get_config_version() < FINAL_CONFIG_VERSION + + def upgrade(self): + with self.lock: + self.print_error('upgrading config') + + self.convert_version_2() + + self.set_key('config_version', FINAL_CONFIG_VERSION, save=True) + + def convert_version_2(self): + if not self._is_upgrade_method_needed(1, 1): + return + + self.rename_config_keys(self.user_config, {'auto_cycle': 'auto_connect'}) + + try: + # change server string FROM host:port:proto TO host:port:s + server_str = self.user_config.get('server') + host, port, protocol = str(server_str).rsplit(':', 2) + assert protocol in ('s', 't') + int(port) # Throw if cannot be converted to int + server_str = '{}:{}:s'.format(host, port) + self._set_key_in_user_config('server', server_str) + except BaseException: + self._set_key_in_user_config('server', None) + + self.set_key('config_version', 2) + + def _is_upgrade_method_needed(self, min_version, max_version): + cur_version = self.get_config_version() + if cur_version > max_version: + return False + elif cur_version < min_version: + raise Exception( + ('config upgrade: unexpected version %d (should be %d-%d)' + % (cur_version, min_version, max_version))) + else: + return True + + def get_config_version(self): + config_version = self.get('config_version', 1) + if config_version > FINAL_CONFIG_VERSION: + self.print_stderr('WARNING: config version ({}) is higher than ours ({})' + .format(config_version, FINAL_CONFIG_VERSION)) + return config_version + def is_modifiable(self, key): - return not key in self.cmdline_options + return key not in self.cmdline_options def save_user_config(self): if not self.path: return path = os.path.join(self.path, "config") s = json.dumps(self.user_config, indent=4, sort_keys=True) - with open(path, "w") as f: - f.write(s) - os.chmod(path, stat.S_IREAD | stat.S_IWRITE) + try: + with open(path, "w", encoding='utf-8') as f: + f.write(s) + os.chmod(path, stat.S_IREAD | stat.S_IWRITE) + except FileNotFoundError: + # datadir probably deleted while running... + if os.path.exists(self.path): # or maybe not? + raise def get_wallet_path(self): """Set the path of the wallet.""" @@ -160,10 +236,14 @@ class SimpleConfig(PrintError): return path # default path + if not os.path.exists(self.path): + raise FileNotFoundError( + _('Electrum-Zcash datadir does not exist. Was it deleted while running?') + '\n' + + _('Should be at {}').format(self.path)) dirpath = os.path.join(self.path, "wallets") if not os.path.exists(dirpath): if os.path.islink(dirpath): - raise BaseException('Dangling link: ' + dirpath) + raise Exception('Dangling link: ' + dirpath) os.mkdir(dirpath) os.chmod(dirpath, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -200,57 +280,203 @@ class SimpleConfig(PrintError): path = wallet.storage.path self.set_key('gui_last_wallet', path) - def max_fee_rate(self): - f = self.get('max_fee_rate', MAX_FEE_RATE) - if f==0: - f = MAX_FEE_RATE - return f + def impose_hard_limits_on_fee(func): + def get_fee_within_limits(self, *args, **kwargs): + fee = func(self, *args, **kwargs) + if fee is None: + return fee + fee = min(FEERATE_MAX_DYNAMIC, fee) + fee = max(FEERATE_DEFAULT_RELAY, fee) + return fee + return get_fee_within_limits - def dynfee(self, i): + @impose_hard_limits_on_fee + def eta_to_fee(self, i): + """Returns fee in sat/kbyte.""" if i < 4: - j = FEE_TARGETS[i] + j = FEE_ETA_TARGETS[i] fee = self.fee_estimates.get(j) else: assert i == 4 fee = self.fee_estimates.get(2) if fee is not None: fee += fee/2 - if fee is not None: - fee = min(5*MAX_FEE_RATE, fee) return fee - def reverse_dynfee(self, fee_per_kb): + def fee_to_depth(self, target_fee): + depth = 0 + for fee, s in self.mempool_fees: + depth += s + if fee <= target_fee: + break + else: + return 0 + return depth + + @impose_hard_limits_on_fee + def depth_to_fee(self, i): + """Returns fee in sat/kbyte.""" + target = self.depth_target(i) + depth = 0 + for fee, s in self.mempool_fees: + depth += s + if depth > target: + break + else: + return 0 + return fee * 1000 + + def depth_target(self, i): + return FEE_DEPTH_TARGETS[i] + + def eta_target(self, i): + if i == len(FEE_ETA_TARGETS): + return 1 + return FEE_ETA_TARGETS[i] + + def fee_to_eta(self, fee_per_kb): import operator - l = list(self.fee_estimates.items()) + [(1, self.dynfee(4))] + l = list(self.fee_estimates.items()) + [(1, self.eta_to_fee(4))] dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), l) min_target, min_value = min(dist, key=operator.itemgetter(1)) if fee_per_kb < self.fee_estimates.get(25)/2: min_target = -1 return min_target + def depth_tooltip(self, depth): + return "%.1f MB from tip"%(depth/1000000) + + def eta_tooltip(self, x): + if x < 0: + return _('Low fee') + elif x == 1: + return _('In the next block') + else: + return _('Within {} blocks').format(x) + + def get_fee_status(self): + dyn = self.is_dynfee() + mempool = self.use_mempool_fees() + pos = self.get_depth_level() if mempool else self.get_fee_level() + fee_rate = self.fee_per_kb() + target, tooltip = self.get_fee_text(pos, dyn, mempool, fee_rate) + return tooltip + ' [%s]'%target if dyn else target + ' [Static]' + + def get_fee_text(self, pos, dyn, mempool, fee_rate): + """Returns (text, tooltip) where + text is what we target: static fee / num blocks to confirm in / mempool depth + tooltip is the corresponding estimate (e.g. num blocks for a static fee) + """ + rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False) + ' sat/byte') if fee_rate is not None else 'unknown' + if dyn: + if mempool: + depth = self.depth_target(pos) + text = self.depth_tooltip(depth) + else: + eta = self.eta_target(pos) + text = self.eta_tooltip(eta) + tooltip = rate_str + else: + text = rate_str + if mempool and self.has_fee_mempool(): + depth = self.fee_to_depth(fee_rate) + tooltip = self.depth_tooltip(depth) + elif not mempool and self.has_fee_etas(): + eta = self.fee_to_eta(fee_rate) + tooltip = self.eta_tooltip(eta) + else: + tooltip = '' + return text, tooltip + + def get_depth_level(self): + maxp = len(FEE_DEPTH_TARGETS) - 1 + return min(maxp, self.get('depth_level', 2)) + + def get_fee_level(self): + maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block" + return min(maxp, self.get('fee_level', 2)) + + def get_fee_slider(self, dyn, mempool): + if dyn: + if mempool: + pos = self.get_depth_level() + maxp = len(FEE_DEPTH_TARGETS) - 1 + fee_rate = self.depth_to_fee(pos) + else: + pos = self.get_fee_level() + maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block" + fee_rate = self.eta_to_fee(pos) + else: + fee_rate = self.fee_per_kb(dyn=False) + pos = self.static_fee_index(fee_rate) + maxp = 9 + return maxp, pos, fee_rate + def static_fee(self, i): - return self.fee_rates[i] + return FEERATE_STATIC_VALUES[i] def static_fee_index(self, value): - dist = list(map(lambda x: abs(x - value), self.fee_rates)) + if value is None: + raise TypeError('static fee cannot be None') + dist = list(map(lambda x: abs(x - value), FEERATE_STATIC_VALUES)) return min(range(len(dist)), key=dist.__getitem__) - def has_fee_estimates(self): - return len(self.fee_estimates)==4 + def has_fee_etas(self): + return len(self.fee_estimates) == 4 + + def has_fee_mempool(self): + return bool(self.mempool_fees) + + def has_dynamic_fees_ready(self): + if self.use_mempool_fees(): + return self.has_fee_mempool() + else: + return self.has_fee_etas() def is_dynfee(self): - return self.get('dynamic_fees', True) + return bool(self.get('dynamic_fees', True)) - def fee_per_kb(self): - dyn = self.is_dynfee() + def use_mempool_fees(self): + return bool(self.get('mempool_fees', False)) + + def fee_per_kb(self, dyn=None, mempool=None): + """Returns sat/kvB fee to pay for a txn. + Note: might return None. + """ + if dyn is None: + dyn = self.is_dynfee() + if mempool is None: + mempool = self.use_mempool_fees() if dyn: - fee_rate = self.dynfee(self.get('fee_level', 2)) + if mempool: + fee_rate = self.depth_to_fee(self.get_depth_level()) + else: + fee_rate = self.eta_to_fee(self.get_fee_level()) else: - fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2) + fee_rate = self.get('fee_per_kb', FEERATE_FALLBACK_STATIC_FEE) return fee_rate + def fee_per_byte(self): + """Returns sat/vB fee to pay for a txn. + Note: might return None. + """ + fee_per_kb = self.fee_per_kb() + return fee_per_kb / 1000 if fee_per_kb is not None else None + def estimate_fee(self, size): - return int(self.fee_per_kb() * size / 1000.) + fee_per_kb = self.fee_per_kb() + if fee_per_kb is None: + raise NoDynamicFeeEstimates() + return self.estimate_fee_for_feerate(fee_per_kb, size) + + @classmethod + def estimate_fee_for_feerate(cls, fee_per_kb, size): + # note: We only allow integer sat/byte values atm. + # The GUI for simplicity reasons only displays integer sat/byte, + # and for the sake of consistency, we thus only use integer sat/byte in + # the backend too. + fee_per_byte = int(fee_per_kb / 1000) + return int(fee_per_byte * size) def update_fee_estimates(self, key, value): self.fee_estimates[key] = value @@ -261,11 +487,7 @@ class SimpleConfig(PrintError): Returns True if an update should be requested. """ now = time.time() - prev_updates = self.fee_estimates_last_updated.values() - oldest_fee_time = min(prev_updates) if prev_updates else 0 - stale_fees = now - oldest_fee_time > 7200 - old_request = now - self.last_time_fee_estimates_requested > 60 - return stale_fees and old_request + return now - self.last_time_fee_estimates_requested > 60 def requested_fee_estimates(self): self.last_time_fee_estimates_requested = time.time() @@ -277,21 +499,6 @@ class SimpleConfig(PrintError): return device -def read_system_config(path=SYSTEM_CONFIG_PATH): - """Parse and return the system config settings in /etc/electrum-zcash.conf.""" - result = {} - if os.path.exists(path): - import configparser - p = configparser.ConfigParser() - try: - p.read(path) - for k, v in p.items('client'): - result[k] = v - except (configparser.NoSectionError, configparser.MissingSectionHeaderError): - pass - - return result - def read_user_config(path): """Parse and store the user config settings in electrum-zcash.conf into user_config[].""" if not path: @@ -300,7 +507,7 @@ def read_user_config(path): if not os.path.exists(config_path): return {} try: - with open(config_path, "r") as f: + with open(config_path, "r", encoding='utf-8') as f: data = f.read() result = json.loads(data) except: diff --git a/lib/storage.py b/lib/storage.py index f33e3ec3..78f3271d 100644 --- 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 +from .util import PrintError, profiler, InvalidPassword, WalletFileException from .plugins import run_hook, plugin_loaders from .keystore import bip44_derivation from . import bitcoin @@ -51,11 +51,20 @@ FINAL_SEED_VERSION = 16 # electrum >= 2.7 will set this to prevent def multisig_type(wallet_type): '''If wallet_type is mofn multi-sig, return [m, n], otherwise return None.''' + if not wallet_type: + return None match = re.match('(\d+)of(\d+)', wallet_type) if match: match = [int(x) for x in match.group(1, 2)] return match +def get_derivation_used_for_hw_device_encryption(): + return ("m" + "/4541509'" # ascii 'ELE' as decimal ("BIP43 purpose") + "/1112098098'") # ascii 'BIE2' as decimal + +# storage encryption version +STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW = range(0, 3) class WalletStorage(PrintError): @@ -68,11 +77,13 @@ class WalletStorage(PrintError): self.modified = False self.pubkey = None if self.file_exists(): - with open(self.path, "r") as f: + with open(self.path, "r", encoding='utf-8') as f: self.raw = f.read() + self._encryption_version = self._init_encryption_version() if not self.is_encrypted(): self.load_data(self.raw) else: + self._encryption_version = STO_EV_PLAINTEXT # avoid new wallets getting 'upgraded' self.put('seed_version', FINAL_SEED_VERSION) @@ -102,15 +113,51 @@ 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() + def is_past_initial_decryption(self): + """Return if storage is in a usable state for normal operations. + + The value is True exactly + if encryption is disabled completely (self.is_encrypted() == False), + or if encryption is enabled but the contents have already been decrypted. + """ + return bool(self.data) + def is_encrypted(self): + """Return if storage encryption is currently enabled.""" + return self.get_encryption_version() != STO_EV_PLAINTEXT + + def is_encrypted_with_user_pw(self): + return self.get_encryption_version() == STO_EV_USER_PW + + def is_encrypted_with_hw_device(self): + return self.get_encryption_version() == STO_EV_XPUB_PW + + def get_encryption_version(self): + """Return the version of encryption used for this storage. + + 0: plaintext / no encryption + + ECIES, private key derived from a password, + 1: password is provided by user + 2: password is derived from an xpub; used with hw wallets + """ + return self._encryption_version + + def _init_encryption_version(self): try: - return base64.b64decode(self.raw)[0:4] == b'BIE1' + magic = base64.b64decode(self.raw)[0:4] + if magic == b'BIE1': + return STO_EV_USER_PW + elif magic == b'BIE2': + return STO_EV_XPUB_PW + else: + return STO_EV_PLAINTEXT except: - return False + return STO_EV_PLAINTEXT def file_exists(self): return self.path and os.path.exists(self.path) @@ -120,20 +167,50 @@ class WalletStorage(PrintError): ec_key = bitcoin.EC_KEY(secret) return ec_key + def _get_encryption_magic(self): + v = self._encryption_version + if v == STO_EV_USER_PW: + return b'BIE1' + elif v == STO_EV_XPUB_PW: + return b'BIE2' + else: + raise WalletFileException('no encryption magic for version: %s' % v) + def decrypt(self, password): ec_key = self.get_key(password) - s = zlib.decompress(ec_key.decrypt_message(self.raw)) if self.raw else None + if self.raw: + enc_magic = self._get_encryption_magic() + s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic)) + else: + s = None self.pubkey = ec_key.get_public_key() s = s.decode('utf8') self.load_data(s) - def set_password(self, password, encrypt): - self.put('use_encryption', bool(password)) - if encrypt and password: + def check_password(self, password): + """Raises an InvalidPassword exception on invalid password""" + if not self.is_encrypted(): + return + if self.pubkey and self.pubkey != self.get_key(password).get_public_key(): + raise InvalidPassword() + + def set_keystore_encryption(self, enable): + self.put('use_encryption', enable) + + def set_password(self, password, enc_version=None): + """Set a password to be used for encrypting this storage.""" + if enc_version is None: + enc_version = self._encryption_version + if password and enc_version != STO_EV_PLAINTEXT: ec_key = self.get_key(password) self.pubkey = ec_key.get_public_key() + self._encryption_version = enc_version else: self.pubkey = None + self._encryption_version = STO_EV_PLAINTEXT + # make sure next storage.write() saves changes + with self.lock: + self.modified = True def get(self, key, default=None): with self.lock: @@ -175,11 +252,12 @@ class WalletStorage(PrintError): if self.pubkey: s = bytes(s, 'utf8') c = zlib.compress(s) - s = bitcoin.encrypt_message(c, self.pubkey) + enc_magic = self._get_encryption_magic() + s = bitcoin.encrypt_message(c, self.pubkey, enc_magic) s = s.decode('utf8') temp_path = "%s.tmp.%s" % (self.path, os.getpid()) - with open(temp_path, "w") as f: + with open(temp_path, "w", encoding='utf-8') as f: f.write(s) f.flush() os.fsync(f.fileno()) @@ -242,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): @@ -263,6 +341,9 @@ class WalletStorage(PrintError): self.write() def convert_wallet_type(self): + if not self._is_upgrade_method_needed(0, 13): + return + wallet_type = self.get('wallet_type') if wallet_type == 'btchip': wallet_type = 'ledger' if self.get('keystore') or self.get('x1/') or wallet_type=='imported': @@ -338,7 +419,7 @@ class WalletStorage(PrintError): d['seed'] = seed self.put(key, d) else: - raise + 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) @@ -445,6 +526,9 @@ class WalletStorage(PrintError): self.put('seed_version', 16) def convert_imported(self): + if not self._is_upgrade_method_needed(0, 13): + return + # '/x' is the internal ID for imported accounts d = self.get('accounts', {}).get('/x', {}).get('imported',{}) if not d: @@ -458,7 +542,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) @@ -468,9 +552,12 @@ 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): + return + self.put('accounts', None) def _is_upgrade_method_needed(self, min_version, max_version): @@ -478,9 +565,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 @@ -496,7 +583,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 >=12: return seed_version if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]: @@ -517,4 +606,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/synchronizer.py b/lib/synchronizer.py index b6534e38..b43fd221 100644 --- a/lib/synchronizer.py +++ b/lib/synchronizer.py @@ -50,6 +50,8 @@ class Synchronizer(ThreadJob): self.requested_histories = {} self.requested_addrs = set() self.lock = Lock() + + self.initialized = False self.initialize() def parse_response(self, response): @@ -84,11 +86,13 @@ class Synchronizer(ThreadJob): return bh2u(hashlib.sha256(status.encode('ascii')).digest()) def on_address_status(self, response): + if self.wallet.synchronizer is None and self.initialized: + return # we have been killed, this was just an orphan callback params, result = self.parse_response(response) if not params: return addr = params[0] - history = self.wallet.get_address_history(addr) + history = self.wallet.history.get(addr, []) if self.get_status(history) != result: if self.requested_histories.get(addr) is None: self.requested_histories[addr] = result @@ -98,12 +102,17 @@ class Synchronizer(ThreadJob): self.requested_addrs.remove(addr) def on_address_history(self, response): + if self.wallet.synchronizer is None and self.initialized: + return # we have been killed, this was just an orphan callback params, result = self.parse_response(response) if not params: return addr = params[0] + server_status = self.requested_histories.get(addr) + if server_status is None: + self.print_error("receiving history (unsolicited)", addr, len(result)) + return self.print_error("receiving history", addr, len(result)) - server_status = self.requested_histories[addr] hashes = set(map(lambda item: item['tx_hash'], result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) # tx_fees @@ -127,6 +136,8 @@ class Synchronizer(ThreadJob): self.requested_histories.pop(addr) def tx_response(self, response): + if self.wallet.synchronizer is None and self.initialized: + return # we have been killed, this was just an orphan callback params, result = self.parse_response(response) if not params: return @@ -177,6 +188,7 @@ class Synchronizer(ThreadJob): if self.requested_tx: self.print_error("missing tx", self.requested_tx) self.subscribe_to_addresses(set(self.wallet.get_addresses())) + self.initialized = True def run(self): '''Called from the network proxy thread main loop.''' diff --git a/lib/tests/__init__.py b/lib/tests/__init__.py index e69de29b..c6e8240f 100644 --- a/lib/tests/__init__.py +++ b/lib/tests/__init__.py @@ -0,0 +1,16 @@ +import unittest + +from lib import constants + + +class TestCaseForTestnet(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + constants.set_testnet() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + constants.set_mainnet() diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py index 0668df88..535938e8 100644 --- a/lib/tests/test_bitcoin.py +++ b/lib/tests/test_bitcoin.py @@ -11,8 +11,12 @@ from lib.bitcoin import ( var_int, op_push, address_to_script, regenerate_key, verify_message, deserialize_privkey, serialize_privkey, is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub, - xpub_type, is_xprv, is_bip32_derivation, seed_type, NetworkConstants) + xpub_type, is_xprv, is_bip32_derivation, seed_type, EncodeBase58Check) from lib.util import bfh +from lib import constants + +from . import TestCaseForTestnet + try: import ecdsa @@ -140,11 +144,11 @@ class Test_bitcoin(unittest.TestCase): self.assertEqual(op_push(0x4b), '4b') self.assertEqual(op_push(0x4c), '4c4c') self.assertEqual(op_push(0xfe), '4cfe') - self.assertEqual(op_push(0xff), '4dff00') + self.assertEqual(op_push(0xff), '4cff') self.assertEqual(op_push(0x100), '4d0001') self.assertEqual(op_push(0x1234), '4d3412') self.assertEqual(op_push(0xfffe), '4dfeff') - self.assertEqual(op_push(0xffff), '4effff0000') + self.assertEqual(op_push(0xffff), '4dffff') self.assertEqual(op_push(0x10000), '4e00000100') self.assertEqual(op_push(0x12345678), '4e78563412') @@ -158,17 +162,7 @@ class Test_bitcoin(unittest.TestCase): self.assertEqual(address_to_script('t3grLzdTrjSSiCFXzxV5YCvkYZt2tJjDLau'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') -class Test_bitcoin_testnet(unittest.TestCase): - - @classmethod - def setUpClass(cls): - super().setUpClass() - NetworkConstants.set_testnet() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - NetworkConstants.set_mainnet() +class Test_bitcoin_testnet(TestCaseForTestnet): def test_address_to_script(self): # base58 P2PKH @@ -250,11 +244,69 @@ class Test_xprv_xpub(unittest.TestCase): self.assertFalse(is_bip32_derivation("")) self.assertFalse(is_bip32_derivation("m/q8462")) + def test_version_bytes(self): + xprv_headers_b58 = { + 'standard': 'xprv', + } + xpub_headers_b58 = { + 'standard': 'xpub', + } + for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + + +class Test_xprv_xpub_testnet(TestCaseForTestnet): + + def test_version_bytes(self): + xprv_headers_b58 = { + 'standard': 'tprv', + } + xpub_headers_b58 = { + 'standard': 'tpub', + } + for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) + + for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items(): + xkey_header_bytes = bfh("%08x" % xkey_header_bytes) + xkey_bytes = xkey_header_bytes + bytes([0] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + + xkey_bytes = xkey_header_bytes + bytes([255] * 74) + xkey_b58 = EncodeBase58Check(xkey_bytes) + self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) + class Test_keyImport(unittest.TestCase): priv_pub_addr = ( {'priv': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6', + 'exported_privkey': 'p2pkh:KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6', 'pub': '02c6467b7e621144105ed3e4835b0b4ab7e35266a2ae1c4f8baa19e9ca93452997', 'address': 't1QTbqnYayRQQ2QZDUgYAcZ6EAcuddyRMbu', 'minikey' : False, @@ -262,7 +314,17 @@ class Test_keyImport(unittest.TestCase): 'compressed': True, 'addr_encoding': 'base58', 'scripthash': 'c9aecd1fef8d661a42c560bf75c8163e337099800b8face5ca3d1393a30508a7'}, + {'priv': 'p2pkh:Kzj8VjwpZ99bQqVeUiRXrKuX9mLr1o6sWxFMCBJn1umC38BMiQTD', + 'exported_privkey': 'p2pkh:Kzj8VjwpZ99bQqVeUiRXrKuX9mLr1o6sWxFMCBJn1umC38BMiQTD', + 'pub': '0352d78b4b37e0f6d4e164423436f2925fa57817467178eca550a88f2821973c41', + 'address': 't1ZQHZQpr51Z83vYLkuCgUnA7xgygSNEL8E', + 'minikey': False, + 'txin_type': 'p2pkh', + 'compressed': True, + 'addr_encoding': 'base58', + 'scripthash': 'a9b2a76fc196c553b352186dfcca81fcf323a721cd8431328f8e9d54216818c1'}, {'priv': '5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD', + 'exported_privkey': 'p2pkh:5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD', 'pub': '04e5fe91a20fac945845a5518450d23405ff3e3e1ce39827b47ee6d5db020a9075422d56a59195ada0035e4a52a238849f68e7a325ba5b2247013e0481c5c7cb3f', 'address': 't1ZFtVnxGSXwNZjnsKVhiAGeEC8nJPuJDDp', 'minikey': False, @@ -270,8 +332,18 @@ class Test_keyImport(unittest.TestCase): 'compressed': False, 'addr_encoding': 'base58', 'scripthash': 'f5914651408417e1166f725a5829ff9576d0dbf05237055bf13abd2af7f79473'}, + {'priv': 'p2pkh:5KhYQCe1xd5g2tqpmmGpUWDpDuTbA8vnpbiCNDwMPAx29WNQYfN', + 'exported_privkey': 'p2pkh:5KhYQCe1xd5g2tqpmmGpUWDpDuTbA8vnpbiCNDwMPAx29WNQYfN', + 'pub': '048f0431b0776e8210376c81280011c2b68be43194cb00bd47b7e9aa66284b713ce09556cde3fee606051a07613f3c159ef3953b8927c96ae3dae94a6ba4182e0e', + 'address': 't1LzMikhRjUTSEzTLdGUBsrBTFZhR9hwffh', + 'minikey': False, + 'txin_type': 'p2pkh', + 'compressed': False, + 'addr_encoding': 'base58', + 'scripthash': '6dd2e07ad2de9ba8eec4bbe8467eb53f8845acff0d9e6f5627391acc22ff62df'}, # from http://bitscan.com/articles/security/spotlight-on-mini-private-keys {'priv': 'SzavMBLoXU6kDrqtUVmffv', + 'exported_privkey': 'p2pkh:L53fCHmQhbNp1B4JipfBtfeHZH7cAibzG9oK19XfiFzxHgAkz6JK', 'pub': '02588d202afcc1ee4ab5254c7847ec25b9a135bbda0f2bc69ee1a714749fd77dc9', 'address': 't1S9WvZLVKoLjXUAxssvGojtqx1pABJMJUU', 'minikey': True, @@ -309,6 +381,7 @@ class Test_keyImport(unittest.TestCase): def test_is_private_key(self): for priv_details in self.priv_pub_addr: self.assertTrue(is_private_key(priv_details['priv'])) + self.assertTrue(is_private_key(priv_details['exported_privkey'])) self.assertFalse(is_private_key(priv_details['pub'])) self.assertFalse(is_private_key(priv_details['address'])) self.assertFalse(is_private_key("not a privkey")) @@ -317,8 +390,7 @@ class Test_keyImport(unittest.TestCase): for priv_details in self.priv_pub_addr: txin_type, privkey, compressed = deserialize_privkey(priv_details['priv']) priv2 = serialize_privkey(privkey, compressed, txin_type) - if not priv_details['minikey']: - self.assertEqual(priv_details['priv'], priv2) + self.assertEqual(priv_details['exported_privkey'], priv2) def test_address_to_scripthash(self): for priv_details in self.priv_pub_addr: diff --git a/lib/tests/test_commands.py b/lib/tests/test_commands.py new file mode 100644 index 00000000..f94a7396 --- /dev/null +++ b/lib/tests/test_commands.py @@ -0,0 +1,33 @@ +import unittest +from decimal import Decimal + +from lib.commands import Commands + + +class TestCommands(unittest.TestCase): + + def test_setconfig_non_auth_number(self): + self.assertEqual(7777, Commands._setconfig_normalize_value('rpcport', "7777")) + self.assertEqual(7777, Commands._setconfig_normalize_value('rpcport', '7777')) + self.assertAlmostEqual(Decimal(2.3), Commands._setconfig_normalize_value('somekey', '2.3')) + + def test_setconfig_non_auth_number_as_string(self): + self.assertEqual("7777", Commands._setconfig_normalize_value('somekey', "'7777'")) + + def test_setconfig_non_auth_boolean(self): + self.assertEqual(True, Commands._setconfig_normalize_value('show_console_tab', "true")) + self.assertEqual(True, Commands._setconfig_normalize_value('show_console_tab', "True")) + + def test_setconfig_non_auth_list(self): + self.assertEqual(['file:///var/www/', 'https://electrum.org'], + Commands._setconfig_normalize_value('url_rewrite', "['file:///var/www/','https://electrum.org']")) + self.assertEqual(['file:///var/www/', 'https://electrum.org'], + Commands._setconfig_normalize_value('url_rewrite', '["file:///var/www/","https://electrum.org"]')) + + def test_setconfig_auth(self): + self.assertEqual("7777", Commands._setconfig_normalize_value('rpcuser', "7777")) + self.assertEqual("7777", Commands._setconfig_normalize_value('rpcuser', '7777')) + self.assertEqual("7777", Commands._setconfig_normalize_value('rpcpassword', '7777')) + self.assertEqual("2asd", Commands._setconfig_normalize_value('rpcpassword', '2asd')) + self.assertEqual("['file:///var/www/','https://electrum.org']", + Commands._setconfig_normalize_value('rpcpassword', "['file:///var/www/','https://electrum.org']")) diff --git a/lib/tests/test_simple_config.py b/lib/tests/test_simple_config.py index c9c648dc..ad7fca2b 100644 --- a/lib/tests/test_simple_config.py +++ b/lib/tests/test_simple_config.py @@ -6,8 +6,7 @@ import tempfile import shutil from io import StringIO -from lib.simple_config import (SimpleConfig, read_system_config, - read_user_config) +from lib.simple_config import (SimpleConfig, read_user_config) class Test_SimpleConfig(unittest.TestCase): @@ -37,18 +36,15 @@ class Test_SimpleConfig(unittest.TestCase): def test_simple_config_key_rename(self): """auto_cycle was renamed auto_connect""" - fake_read_system = lambda : {} fake_read_user = lambda _: {"auto_cycle": True} read_user_dir = lambda : self.user_dir config = SimpleConfig(options=self.options, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(config.get("auto_connect"), True) self.assertEqual(config.get("auto_cycle"), None) fake_read_user = lambda _: {"auto_connect": False, "auto_cycle": True} config = SimpleConfig(options=self.options, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(config.get("auto_connect"), False) @@ -57,110 +53,51 @@ class Test_SimpleConfig(unittest.TestCase): def test_simple_config_command_line_overrides_everything(self): """Options passed by command line override all other configuration sources""" - fake_read_system = lambda : {"electrum_path": "a"} fake_read_user = lambda _: {"electrum_path": "b"} read_user_dir = lambda : self.user_dir config = SimpleConfig(options=self.options, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(self.options.get("electrum_path"), config.get("electrum_path")) - def test_simple_config_user_config_overrides_system_config(self): - """Options passed in user config override system config.""" - fake_read_system = lambda : {"electrum_path": self.electrum_dir} - fake_read_user = lambda _: {"electrum_path": "b"} - read_user_dir = lambda : self.user_dir - config = SimpleConfig(options={}, - read_system_config_function=fake_read_system, - read_user_config_function=fake_read_user, - read_user_dir_function=read_user_dir) - self.assertEqual("b", config.get("electrum_path")) - - def test_simple_config_system_config_ignored_if_portable(self): - """If electrum is started with the "portable" flag, system - configuration is completely ignored.""" - fake_read_system = lambda : {"some_key": "some_value"} - fake_read_user = lambda _: {} - read_user_dir = lambda : self.user_dir - config = SimpleConfig(options={"portable": True}, - read_system_config_function=fake_read_system, - read_user_config_function=fake_read_user, - read_user_dir_function=read_user_dir) - self.assertEqual(config.get("some_key"), None) - def test_simple_config_user_config_is_used_if_others_arent_specified(self): """If no system-wide configuration and no command-line options are specified, the user configuration is used instead.""" - fake_read_system = lambda : {} fake_read_user = lambda _: {"electrum_path": self.electrum_dir} read_user_dir = lambda : self.user_dir config = SimpleConfig(options={}, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(self.options.get("electrum_path"), config.get("electrum_path")) def test_cannot_set_options_passed_by_command_line(self): - fake_read_system = lambda : {} fake_read_user = lambda _: {"electrum_path": "b"} read_user_dir = lambda : self.user_dir config = SimpleConfig(options=self.options, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) config.set_key("electrum_path", "c") self.assertEqual(self.options.get("electrum_path"), config.get("electrum_path")) - def test_can_set_options_from_system_config(self): - fake_read_system = lambda : {"electrum_path": self.electrum_dir} - fake_read_user = lambda _: {} - read_user_dir = lambda : self.user_dir - config = SimpleConfig(options={}, - read_system_config_function=fake_read_system, - read_user_config_function=fake_read_user, - read_user_dir_function=read_user_dir) - config.set_key("electrum_path", "c") - self.assertEqual("c", config.get("electrum_path")) - def test_can_set_options_set_in_user_config(self): another_path = tempfile.mkdtemp() - fake_read_system = lambda : {} fake_read_user = lambda _: {"electrum_path": self.electrum_dir} read_user_dir = lambda : self.user_dir config = SimpleConfig(options={}, - read_system_config_function=fake_read_system, - read_user_config_function=fake_read_user, - read_user_dir_function=read_user_dir) - config.set_key("electrum_path", another_path) - self.assertEqual(another_path, config.get("electrum_path")) - - def test_can_set_options_from_system_config_if_portable(self): - """If the "portable" flag is set, the user can overwrite system - configuration options.""" - another_path = tempfile.mkdtemp() - fake_read_system = lambda : {"electrum_path": self.electrum_dir} - fake_read_user = lambda _: {} - read_user_dir = lambda : self.user_dir - config = SimpleConfig(options={"portable": True}, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) config.set_key("electrum_path", another_path) self.assertEqual(another_path, config.get("electrum_path")) def test_user_config_is_not_written_with_read_only_config(self): - """The user config does not contain command-line options or system - options when saved.""" - fake_read_system = lambda : {"something": "b"} + """The user config does not contain command-line options when saved.""" fake_read_user = lambda _: {"something": "a"} read_user_dir = lambda : self.user_dir self.options.update({"something": "c"}) config = SimpleConfig(options=self.options, - read_system_config_function=fake_read_system, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) config.save_user_config() @@ -168,48 +105,10 @@ class Test_SimpleConfig(unittest.TestCase): with open(os.path.join(self.electrum_dir, "config"), "r") as f: contents = f.read() result = ast.literal_eval(contents) + result.pop('config_version', None) self.assertEqual({"something": "a"}, result) -class TestSystemConfig(unittest.TestCase): - - sample_conf = """ -[client] -gap_limit = 5 - -[something_else] -everything = 42 -""" - - def setUp(self): - super(TestSystemConfig, self).setUp() - self.thefile = tempfile.mkstemp(suffix=".electrum.test.conf")[1] - - def tearDown(self): - super(TestSystemConfig, self).tearDown() - os.remove(self.thefile) - - def test_read_system_config_file_does_not_exist(self): - somefile = "/foo/I/do/not/exist/electrum.conf" - result = read_system_config(somefile) - self.assertEqual({}, result) - - def test_read_system_config_file_returns_file_options(self): - with open(self.thefile, "w") as f: - f.write(self.sample_conf) - - result = read_system_config(self.thefile) - self.assertEqual({"gap_limit": "5"}, result) - - def test_read_system_config_file_no_sections(self): - - with open(self.thefile, "w") as f: - f.write("gap_limit = 5") # The file has no sections at all - - result = read_system_config(self.thefile) - self.assertEqual({}, result) - - class TestUserConfig(unittest.TestCase): def setUp(self): diff --git a/lib/tests/test_storage_upgrade.py b/lib/tests/test_storage_upgrade.py index a91b8817..fb2cd770 100644 --- a/lib/tests/test_storage_upgrade.py +++ b/lib/tests/test_storage_upgrade.py @@ -12,12 +12,12 @@ from lib.tests.test_wallet import WalletTestCase class TestStorageUpgrade(WalletTestCase): def test_upgrade_from_client_1_9_8_seeded(self): - wallet_str = "{'addr_history':{'177hEYTccmuYH8u68pYfaLteTxwJrVgvJj':[],'15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc':[],'1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf':[],'1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs':[],'1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC':[],'1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm':[],'1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj':[],'1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa':[]},'accounts_expanded':{},'master_public_key':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb','use_encryption':False,'seed':'2605aafe50a45bdf2eb155302437e678','accounts':{0:{0:['1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC','1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj','177hEYTccmuYH8u68pYfaLteTxwJrVgvJj','1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm','15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc'],1:['1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs','1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa','1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf']}},'seed_version':4}" + wallet_str = "{'addr_history':{'t1PzJEsskb6h8smwz5FMni9zZid8PixVmkC':[],'t1NMiNCpT1FWqgDHRGv589eowuDaejF1vbK':[],'t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j':[],'t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq':[],'t1WcVUY2YvGmkduVH6Y8kExVH3WuCgCd7nw':[],'t1g8qguoPyr6SNYVd2Z5w3czRuCwHex7ofk':[],'t1g3HpjNvJNotQgfQuCFnm1oK6VTo8qAKbA':[],'t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu':[]},'accounts_expanded':{},'master_public_key':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb','use_encryption':False,'seed':'2605aafe50a45bdf2eb155302437e678','accounts':{0:{0:['t1WcVUY2YvGmkduVH6Y8kExVH3WuCgCd7nw','t1g3HpjNvJNotQgfQuCFnm1oK6VTo8qAKbA','t1PzJEsskb6h8smwz5FMni9zZid8PixVmkC','t1g8qguoPyr6SNYVd2Z5w3czRuCwHex7ofk','t1NMiNCpT1FWqgDHRGv589eowuDaejF1vbK'],1:['t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq','t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu','t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j']}},'seed_version':4}" self._upgrade_storage(wallet_str) # TODO pre-2.0 mixed wallets are not split currently #def test_upgrade_from_client_1_9_8_mixed(self): - # wallet_str = "{'addr_history':{'15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc':[],'177hEYTccmuYH8u68pYfaLteTxwJrVgvJj':[],'1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC':[],'1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm':[],'1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj':[],'1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf':[],'1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs':[],'1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa':[]},'accounts_expanded':{},'master_public_key':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb','use_encryption':False,'seed':'2605aafe50a45bdf2eb155302437e678','accounts':{0:{0:['1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC','1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj','177hEYTccmuYH8u68pYfaLteTxwJrVgvJj','1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm','15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc'],1:['1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs','1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa','1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf'],'mpk':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb'}},'imported_keys':{'15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA':'5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq','1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6':'L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U','1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr':'L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM'},'seed_version':4}" + # wallet_str = "{'addr_history':{'t1NMiNCpT1FWqgDHRGv589eowuDaejF1vbK':[],'t1PzJEsskb6h8smwz5FMni9zZid8PixVmkC':[],'t1WcVUY2YvGmkduVH6Y8kExVH3WuCgCd7nw':[],'t1g8qguoPyr6SNYVd2Z5w3czRuCwHex7ofk':[],'t1g3HpjNvJNotQgfQuCFnm1oK6VTo8qAKbA':[],'t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j':[],'t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq':[],'t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu':[]},'accounts_expanded':{},'master_public_key':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb','use_encryption':False,'seed':'2605aafe50a45bdf2eb155302437e678','accounts':{0:{0:['t1WcVUY2YvGmkduVH6Y8kExVH3WuCgCd7nw','t1g3HpjNvJNotQgfQuCFnm1oK6VTo8qAKbA','t1PzJEsskb6h8smwz5FMni9zZid8PixVmkC','t1g8qguoPyr6SNYVd2Z5w3czRuCwHex7ofk','t1NMiNCpT1FWqgDHRGv589eowuDaejF1vbK'],1:['t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq','t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu','t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j'],'mpk':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb'}},'imported_keys':{'t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T':'5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq','t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM':'L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U','t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn':'L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM'},'seed_version':4}" # self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_0_4_seeded(self): @@ -25,23 +25,23 @@ class TestStorageUpgrade(WalletTestCase): self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_0_4_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"use_encryption":false,"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"use_encryption":false,"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_0_4_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_0_4_trezor_singleacc(self): - wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG"],"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","0t3KUGeN5k6TMfgH43XzqAAw6zLFJEWApDRi061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","t1Rae1B4LfR4LVSAuGMJhnFT3nqfFYaNmHh"],"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_0_4_trezor_multiacc(self): - wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]]},"labels":{"0":"Main account","1":"acc1"},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ"],"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]]},"labels":{"0":"Main account","1":"acc1"},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk"],"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_0_4_multisig(self): - wallet_str = '{"accounts":{"0":{"change":[["03c3a8549f35d7842192e7e00afa25ef1c779d05f1c891ba7c30de968fb29e3e78","02e191e105bccf1b4562d216684632b9ec22c87e1457b537eb27516afa75c56831"],["03793397f02b3bd3d0f6f0dafc7d42b9701234a269805d89efbbc2181683368e4b","02153705b8e4df41dc9d58bc0360c79a9209b3fc289ec54118f0b149d5a3b3546d"],["02511e8cfb39c8ce1c790f26bcab68ba5d5f79845ec1c6a92b0ac9f331648d866a","02c29c1ea70e23d866204a11ec8d8ecd70d6f51f58dd8722824cacb1985d4d1870"]],"receiving":[["0283ce4f0f12811e1b27438a3edb784aeb600ca7f4769c9c49c3e704e216421d3e","03a1bbada7401cade3b25a23e354186c772e2ba4ac0d9c0447627f7d540eb9891d"],["0286b45a0bcaa215716cbc59a22b6b1910f9ebad5884f26f55c2bb38943ee8fdb6","02799680336c6bd19005588fad12256223cb8a416649d60ea5d164860c0872b931"],["039e2bf377709e41bba49fb1f3f873b9b87d50ae3b574604cd9b96402211ea1f36","02ef9ceaaf754ba46f015e1d704f1a06157cc4441da0cfaf096563b22ec225ca5f"],["025220baaca5bff1a5ffbf4d36e9fcc6f5d05f4af750ef29f6d88d9b5f95fef79a","02350c81bebfa3a894df69302a6601175731d443948a12d8ec7860981988e3803e"],["028fd6411534d722b625482659de54dd609f5b5c935ae8885ca24bfd3266210527","03b9c7780575f17e64f9dfd5947945b1dbdb65aecef562ac076335fd7aa09844e4"],["0353066065985ec06dbef33e7a081d9240023891a51c4e9eda7b3eb1b4af165e04","028c3fa7622e4c8bac07a2c549885a045532e67a934ca10e20729d0fdfe3a75339"],["02253b4eabf2834af86b409d5ca8e671de9a75c3937bff2dac9521c377ca195668","02d5e83c445684eb502049f48e621e1ca16e07e5dc4013c84d661379635f58877b"],["030d38e4c7a5c7c9551adcace3b70dcaa02bf841febd6dc308f3abd7b7bf2bdc49","0375a0b50cd7f3af51550207a766c5db326b2294f5a4b456a90190e4fbeb720d97"],["0327280215ba4a0d8c404085c4f6091906a9e1ada7ce4202a640ac701446095954","037cd9b5e6664d28a61e01626056cdb7e008815b365c8b65fa50ac44d6c1ad126e"],["02f80a80146674da828fc67a062d1ab47fb0714cf40ec5c517ee23ea71d3033474","03fd8ab9bc9458b87e0b7b2a46ea6b46de0a5f6ecaf1a204579698bfa881ff93ce"],["034965bd56c6ca97e0e5ffa79cdc1f15772fa625b76da84cc8adb1707e2e101775","033e13cb19d930025bfc801b829e64d12934f9f19df718f4ea6160a4fb61320a9c"],["034de271009a06d733de22601c3d3c6fe8b3ec5a44f49094ac002dc1c90a3b096d","023f0b2f653c0fdbdc292040fee363ceaa5828cfd8e012abcf6cd9bad2eaa3dc72"],["022aec8931c5b17bdcdd6637db34718db6f267cb0a55a611eb6602e15deb6ed4df","021de5d4bbb73b6dfab2c0df6970862b08130902ff3160f31681f34aecf39721f6"],["02a0e3b52293ec73f89174ff6e5082fcfebc45f2fdd9cfe12a6981aa120a7c1fa7","0371d41b5f18e8e1990043c1e52f998937bc7e81b8ace4ddfc5cd0d029e4c81894"],["030bc1cbe4d750067254510148e3af9bc84925cdd17db3b54d9bbf4a409b83719a","0371c4800364a8a32bfbda7ea7724c1f5bdbd794df8a6080a3bd3b52c52cf32402"],["0318c5cd5f19ff037e3dec3ce5ac1a48026f5a58c4129271b12ae22f8542bcd718","03b5c70db71d520d04f810742e7a5f42d810e94ec6cbf4b48fa6dd7b4d425e76c1"],["0213f68b86a8c4a0840fa88d9a06904c59292ec50172813b8cca62768f3b708811","0353037209eb400ba7fcfa9f296a8b2745e1bbcbfb28c4adebf74de2e0e6a58c00"],["028decff8a7f5a7982402d95b050fbc9958e449f154990bbfe0f553a1d4882fd03","025ecd14812876e885d8f54cab30d1c2a8ae6c6ed0847e96abd65a3700148d94e2"],["0267f8dab8fdc1df4231414f31cfeb58ce96f3471ba78328cd429263d151c81fed","03e0d01df1fd9e958a7324d29afefbc76793a40447a2625c494355c577727d69ba"],["03de3c4d173b27cdfdd8e56fbf3cd6ee8729b94209c20e5558ddd7a76281a37e2e","0218ccb595d7fa559f0bae1ea76d19526980b027fb9be009b6b486d8f8eb0e00d5"]],"xpub":"xpub661MyMwAqRbcFUEYv1psxyPnjiHhTYe85AwFRs5jShbpgrfQ9UXBmxantqgGT3oAVLiHDYoR3ruT3xRGcxsmBMJxyg94FGcxF86QnzYDc6e","xpub2":"xpub661MyMwAqRbcGFd5DccFn4YW2HEdPhVZ2NEBAn416bvDFBi8HN5udmB6DkWpuXFtXaXZdq9UvMoiHxaauk6R1CZgKUR8vpng4LoudP4YVXA"}},"master_private_keys":{"x1/":"xprv9s21ZrQH143K2zA5ozHsbqT4BgTD45vGhx1edUg7tN4qp4LFbwCwEAGK3ZVaBaCRQnuy7AJ7qbPGxKiynNtGd7CzjBXEV4mEwStnPo98Xve"},"master_public_keys":{"x1/":"xpub661MyMwAqRbcFUEYv1psxyPnjiHhTYe85AwFRs5jShbpgrfQ9UXBmxantqgGT3oAVLiHDYoR3ruT3xRGcxsmBMJxyg94FGcxF86QnzYDc6e","x2/":"xpub661MyMwAqRbcGFd5DccFn4YW2HEdPhVZ2NEBAn416bvDFBi8HN5udmB6DkWpuXFtXaXZdq9UvMoiHxaauk6R1CZgKUR8vpng4LoudP4YVXA"},"seed":"start accuse bounce inhale crucial infant october radar enforce stage dumb spot account","seed_version":11,"use_encryption":false,"wallet_type":"2of2"}' + wallet_str = '{"accounts":{"0":{"change":[["03c3a8549f35d7842192e7e00afa25ef1c779d05f1c891ba7c30de968fb29e3e78","02e191e105bccf1b4562d216684632b9ec22c87e1457b537eb27516afa75c56831"],["03793397f02b3bd3d0f6f0dafc7d42b9701234a269805d89efbbc2181683368e4b","02153705b8e4df41dc9d58bc0360c79a9209b3fc289ec54118f0b149d5a3b3546d"],["02511e8cfb39c8ce1c790f26bcab68ba5d5f79845ec1c6a92b0ac9f331648d866a","02c29c1ea70e23d866204a11ec8d8ecd70d6f51f58dd8722824cacb1985d4d1870"]],"receiving":[["0283ce4f0f12811e1b27438a3edb784aeb600ca7f4769c9c49c3e704e216421d3e","03a1bbada7401cade3b25a23e354186c772e2ba4ac0d9c0447627f7d540eb9891d"],["0286b45a0bcaa215716cbc59a22b6b1910f9ebad5884f26f55c2bb38943ee8fdb6","02799680336c6bd19005588fad12256223cb8a416649d60ea5d164860c0872b931"],["039e2bf377709e41bba49fb1f3f873b9b87d50ae3b574604cd9b96402211ea1f36","02ef9ceaaf754ba46f015e1d704f1a06157cc4441da0cfaf096563b22ec225ca5f"],["025220baaca5bff1a5ffbf4d36e9fcc6f5d05f4af750ef29f6d88d9b5f95fef79a","02350c81bebfa3a894df69302a6601175731d443948a12d8ec7860981988e3803e"],["028fd6411534d722b625482659de54dd609f5b5c935ae8885ca24bfd3266210527","03b9c7780575f17e64f9dfd5947945b1dbdb65aecef562ac076335fd7aa09844e4"],["0353066065985ec06dbef33e7a081d9240023891a51c4e9eda7b3eb1b4af165e04","028c3fa7622e4c8bac07a2c549885a045532e67a934ca10e20729d0fdfe3a75339"],["02253b4eabf2834af86b409d5ca8e671de9a75c3937bff2dac9521c377ca195668","02d5e83c445684eb502049f48e621e1ca16e07e5dc4013c84d661379635f58877b"],["030d38e4c7a5c7c9551adcace3b70dcaa02bf841febd6dc308f3abd7b7bf2bdc49","0375a0b50cd7f3af51550207a766c5db326b2294f5a4b456a90190e4fbeb720d97"],["0327280215ba4a0d8c404085c4f6091906a9e1ada7ce4202a640ac701446095954","037cd9b5e6664d28a61e01626056cdb7e008815b365c8b65fa50ac44d6c1ad126e"],["02f80a80146674da828fc67a062d1ab47fb0714cf40ec5c517ee23ea71d3033474","03fd8ab9bc9458b87e0b7b2a46ea6b46de0a5f6ecaf1a204579698bfa881ff93ce"],["034965bd56c6ca97e0e5ffa79cdc1f15772fa625b76da84cc8adb1707e2e101775","033e13cb19d930025bfc801b829e64d12934f9f19df718f4ea6160a4fb61320a9c"],["034de271009a06d733de22601c3d3c6fe8b3ec5a44f49094ac002dc1c90a3b096d","023f0b2f653c0fdbdc292040fee363ceaa5828cfd8e012abcf6cd9bad2eaa3dc72"],["022aec8931c5b17bdcdd6637db34718db6f267cb0a55a611eb6602e15deb6ed4df","021de5d4bbb73b6dfab2c0df6970862b08130902ff3160f31681f34aecf39721f6"],["02a0e3b52293ec73f89174ff6e5082fcfebc45f2fdd9cfe12a6981aa120a7c1fa7","0371d41b5f18e8e1990043c1e52f998937bc7e81b8ace4ddfc5cd0d029e4c81894"],["030bc1cbe4d750067254510t1M1F3v5HZvuejf8Wa3pEmQ9WKipEJ9n9zw09b83719a","0371c4800364a8a32bfbda7ea7724c1f5bdbd794df8a6080a3bd3b52c52cf32402"],["0318c5cd5f19ff037e3dec3ce5ac1a48026f5a58c4129271b12ae22f8542bcd718","03b5c70db71d520d04f810742e7a5f42d810e94ec6cbf4b48fa6dd7b4d425e76c1"],["0213f68b86a8c4a0840fa88d9a06904c59292ec50172813b8cca62768f3b708811","0353037209eb400ba7fcfa9f296a8b2745e1bbcbfb28c4adebf74de2e0e6a58c00"],["028decff8a7f5a7982402d95b050fbc9958e449f154990bbfe0f553a1d4882fd03","025ecd14812876e885d8f54cab30d1c2a8ae6c6ed0847e96abd65a3700148d94e2"],["0267f8dab8fdc1df4231414f31cfeb58ce96f3471ba78328cd429263d151c81fed","03e0d01df1fd9e958a7324d29afefbc76793a40447a2625c494355c577727d69ba"],["03de3c4d173b27cdfdd8e56fbf3cd6ee8729b94209c20e5558ddd7a76281a37e2e","0218ccb595d7fa559f0bae1ea76d19526980b027fb9be009b6b486d8f8eb0e00d5"]],"xpub":"xpub661MyMwAqRbcFUEYv1psxyPnjiHhTYe85AwFRs5jShbpgrfQ9UXBmxantqgGT3oAVLiHDYoR3ruT3xRGcxsmBMJxyg94FGcxF86QnzYDc6e","xpub2":"xpub661MyMwAqRbcGFd5DccFn4YW2HEdPhVZ2NEBAn416bvDFBi8HN5udmB6DkWpuXFtXaXZdq9UvMoiHxaauk6R1CZgKUR8vpng4LoudP4YVXA"}},"master_private_keys":{"x1/":"xprv9s21ZrQH143K2zA5ozHsbqT4BgTD45vGhx1edUg7tN4qp4LFbwCwEAGK3ZVaBaCRQnuy7AJ7qbPGxKiynNtGd7CzjBXEV4mEwStnPo98Xve"},"master_public_keys":{"x1/":"xpub661MyMwAqRbcFUEYv1psxyPnjiHhTYe85AwFRs5jShbpgrfQ9UXBmxantqgGT3oAVLiHDYoR3ruT3xRGcxsmBMJxyg94FGcxF86QnzYDc6e","x2/":"xpub661MyMwAqRbcGFd5DccFn4YW2HEdPhVZ2NEBAn416bvDFBi8HN5udmB6DkWpuXFtXaXZdq9UvMoiHxaauk6R1CZgKUR8vpng4LoudP4YVXA"},"seed":"start accuse bounce inhale crucial infant october radar enforce stage dumb spot account","seed_version":11,"use_encryption":false,"wallet_type":"2of2"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_1_1_seeded(self): @@ -49,19 +49,19 @@ class TestStorageUpgrade(WalletTestCase): self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_1_1_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_1_1_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_1_1_trezor_singleacc(self): - wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG"],"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","0t3KUGeN5k6TMfgH43XzqAAw6zLFJEWApDRi061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","t1Rae1B4LfR4LVSAuGMJhnFT3nqfFYaNmHh"],"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_1_1_trezor_multiacc(self): - wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC":[],"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz":[],"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM":[],"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ":[],"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S":[],"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH":[],"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw":[],"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb":[],"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX":[],"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ":[],"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp":[],"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk":[],"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD":[],"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp":[],"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz":[],"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid":[],"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu":[],"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo":[],"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj":[],"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv":[],"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC":[],"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe":[],"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG":[]},"labels":{},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ"],"pruned_txo":{},"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy":[],"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu":[],"t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5":[],"t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM":[],"t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce":[],"t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ":[],"t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49":[],"t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw":[],"t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk":[],"t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq":[],"t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P":[],"t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C":[],"t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i":[],"t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth":[],"t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby":[],"t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF":[],"t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE":[],"t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi":[],"t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn":[],"t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8":[],"t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM":[],"t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R":[],"t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya":[]},"labels":{},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk"],"pruned_txo":{},"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_1_1_multisig(self): @@ -73,19 +73,19 @@ class TestStorageUpgrade(WalletTestCase): self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_2_0_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":489714,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":489714,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_2_0_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_2_0_trezor_singleacc(self): - wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG"],"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","0t3KUGeN5k6TMfgH43XzqAAw6zLFJEWApDRi061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","t1Rae1B4LfR4LVSAuGMJhnFT3nqfFYaNmHh"],"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_2_0_trezor_multiacc(self): - wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC":[],"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz":[],"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM":[],"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ":[],"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S":[],"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH":[],"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw":[],"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb":[],"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX":[],"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ":[],"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp":[],"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk":[],"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD":[],"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp":[],"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz":[],"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid":[],"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu":[],"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo":[],"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj":[],"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv":[],"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC":[],"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe":[],"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG":[]},"labels":{},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ"],"pruned_txo":{},"stored_height":490006,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy":[],"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu":[],"t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5":[],"t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM":[],"t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce":[],"t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ":[],"t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49":[],"t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw":[],"t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk":[],"t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq":[],"t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P":[],"t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C":[],"t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i":[],"t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth":[],"t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby":[],"t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF":[],"t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE":[],"t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi":[],"t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn":[],"t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8":[],"t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM":[],"t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R":[],"t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya":[]},"labels":{},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk"],"pruned_txo":{},"stored_height":490006,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_2_0_multisig(self): @@ -97,19 +97,19 @@ class TestStorageUpgrade(WalletTestCase): self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_3_2_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":489715,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":489715,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_3_2_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_3_2_trezor_singleacc(self): - wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1","029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156","034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e","036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG"],"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1","029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156","034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e","036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","0t3KUGeN5k6TMfgH43XzqAAw6zLFJEWApDRi061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","t1Rae1B4LfR4LVSAuGMJhnFT3nqfFYaNmHh"],"pruned_txo":{},"stored_height":0,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_3_2_trezor_multiacc(self): - wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8","03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff","03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7","022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC":[],"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz":[],"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM":[],"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ":[],"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6":[],"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S":[],"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH":[],"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw":[],"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb":[],"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX":[],"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ":[],"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp":[],"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk":[],"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD":[],"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp":[],"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz":[],"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs":[],"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid":[],"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu":[],"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo":[],"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj":[],"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv":[],"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC":[],"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe":[],"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL":[],"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG":[]},"labels":{},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ"],"pruned_txo":{},"stored_height":490008,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8","03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff","03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7","022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy":[],"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu":[],"t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5":[],"t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM":[],"t1Njrm3uzEuQg1MNapyYnpXEMhJRkCAxc1d":[],"t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce":[],"t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ":[],"t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49":[],"t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw":[],"t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk":[],"t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq":[],"t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P":[],"t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C":[],"t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i":[],"t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth":[],"t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby":[],"t1cgVBNfdDwLVsoYXMnymANTvc5RuFGLm1w":[],"t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF":[],"t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE":[],"t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi":[],"t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn":[],"t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8":[],"t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM":[],"t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R":[],"t1fSU5JTkH2hM6GNLUeWhrAPcdikYsBwgdX":[],"t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya":[]},"labels":{},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk"],"pruned_txo":{},"stored_height":490008,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_3_2_multisig(self): @@ -121,19 +121,19 @@ class TestStorageUpgrade(WalletTestCase): self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_4_3_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"stored_height":477636,"use_encryption":false,"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"stored_height":477636,"use_encryption":false,"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_4_3_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":490038,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"stored_height":490038,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_4_3_trezor_singleacc(self): - wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1","029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156","034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e","036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG"],"pruned_txo":{},"stored_height":485855,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1","029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156","034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e","036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","0t3KUGeN5k6TMfgH43XzqAAw6zLFJEWApDRi061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","t1Rae1B4LfR4LVSAuGMJhnFT3nqfFYaNmHh"],"pruned_txo":{},"stored_height":485855,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_4_3_trezor_multiacc(self): - wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8","03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff","03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7","022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]]},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ"],"pruned_txo":{},"stored_height":490009,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' + wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8","03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff","03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7","022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]]},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk"],"pruned_txo":{},"stored_height":490009,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor"}''' self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_4_3_multisig(self): @@ -141,63 +141,63 @@ class TestStorageUpgrade(WalletTestCase): self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_5_4_seeded(self): - wallet_str = '{"accounts":{"0":{"change":["0253e61683b66ebf5a4916334adf1409ffe031016717868c9600d313e87538e745","021762e47578385ecedc03c7055da1713971c82df242920e7079afaf153cc37570","0303a8d6a35956c228aa95a17aab3dee0bca255e8b4f7e8155b23acef15cf4a974","02e881bc60018f9a6c566e2eb081a670f48d89b4a6615466788a4e2ce20246d4c6","02f0090e29817ef64c17f27bf6cdebc1222f7e11d7112073f45708e8d218340777","035b9c53b85fd0c2b434682675ac862bfcc7c5bb6993aee8e542f01d96ff485d67"],"receiving":["024fbc610bd51391794c40a7e04b0e4d4adeb6b0c0cc84ac0b3dad90544e428c47","024a2832afb0a366b149b6a64b648f0df0d28c15caa77f7bbf62881111d6915fe9","028cd24716179906bee99851a9062c6055ec298a3956b74631e30f5239a50cb328","039761647d7584ba83386a27875fe3d7715043c2817f4baca91e7a0c81d164d73d","02606fc2f0ce90edc495a617329b3c5c5cc46e36d36e6c66015b1615137278eabd","02191cc2986e33554e7b155f9eddcc3904fdba43a5a3638499d3b7b5452692b740","024b5bf755b2f65cab1f7e5505febc1db8b91781e5aac352902e79bc96ad7d9ad0","0309816cb047402b84133f4f3c5e56c215e860204513278beef54a87254e44c14a","03f53d34337c12ddb94950b1fee9e4a9cf06ad591db66194871d31a17ec7b59ac7","0325ede4b08073d7f288741c2c577878919fd5d832a9e6e04c9eac5563ae13aa83","02eca43081b04f68d6c8b81781acd59e5b8d2ba44dba195369afc40790fd9edef7","029a8ca96c64d3a98345be1594208908f2be5e6af6bcc6ff3681f271e75fcf232e","02fbe0804980750163a216cc91cfe86e907addf0e80797a8ea5067977eb4897c1b","0344f32fc1ee8b2eb08f419325529f495d77a3b5ea683bbce7a44178705ab59302","021dd62bdf18256bd5316ce3cbcca58785378058a41ba2d1c58f4cc76449b3c424","035e61cdbdb4306e58a816a19ad92c7ca3a392b67ac6d7257646868ffe512068c5","0326a4db82f21787d0246f8144abe6cda124383b7d93a6536f36c05af530ea262a","02b352a27a8f9c57b8e5c89b357ba9d0b5cb18bf623509b34cd881fcf8b89a819a","02a59188edef1ed29c158a0adb970588d2031cfe53e72e83d35b7e8dd0c0c77525","02e8b9e42a54d072c8887542c405f6c99cfabf41bdde639944b44ba7408837afd1"],"xpub":"xpub661MyMwAqRbcGh7ywNf1BYoFCs8mht2YnvkMYUJTazrAWbnbvkrisvSvrKGjRTDtw324xzprbDgphsmPv2pB6K5Sux3YNHC8pnJANCBY6vG"}},"accounts_expanded":{},"addr_history":{"12LXoVHUnAXn6BVBpshjwd7sSTwp5nsd7W":[],"12iXPYBErR6ZMESB9Nv74S4pVxdGMNLiW2":[],"13jmb5Vc2qh29tPhg637BwCJN7hStGWYXE":[],"14dHBBbwFVC7niSCqrb5HCHRK5K8rrgaW6":[],"14xsHuYGs4gKpRK3deuYwhMBTAwUeu2dpB":[],"15MpWMUasNVPTpzC5hK2AuVFwQ3AHd8fkv":[],"17nmvao3F84ebPrcv1LUxPUSS94U9EvCUt":[],"17yotEc8oUgJVQUnkjZSQjcqqZEbFFnXx8":[],"1A3c1rCCS2MYYobffyUHwPqkqE5ZpvG8Um":[],"1AtCzmcth79q6HgeyDnM3NLfr29hBHcfcg":[],"1AufJhUsMbqwbLK9JzUGQ9tTwphCQiVCwD":[],"1B77DkhJ8qHcwPQC2c1HyuNcYu5TzxxaJ7":[],"1D4bgjc4MDtEPWNTVfqG5bAodVu3D1Gjft":[],"1DefMPXdeCSQC5ieu8kR7hNGAXykNzWXpm":[],"1E673RESY1SvTWwUr5hQ1E7dGiRiSgkYFP":[],"1Ex6hnmpgp3FQrpR5aYvp9zpXemFiH7vky":[],"1FH2iAc5YgJKj1KcpJ1djuW3wJ2GbQezAv":[],"1GpjShJMGrLQGP6nZFDEswU7qUUgJbNRKi":[],"1H4BtV4Grfq2azQgHSNziN7MViQMDR9wxd":[],"1HnWq29dPuDRA7gx9HQLySGdwGWiNx4UP1":[],"1LMuebyhm8vnuw5qX3tqU2BhbacegeaFuE":[],"1LTJK8ffwJzRaNR5dDEKqJt6T8b4oVbaZx":[],"1LtXYvRr4j1WpLLA398nbmKhzhqq4abKi8":[],"1NfsUmibBxnuA3ir8GJvPUtY5czuiCfuYK":[],"1Q3cZjzADnnx5pcc1NN2ekJjLijNjXMXfr":[],"1okpBWorqo5WsBf5KmocsfhBCEDhNstW2":[]},"master_private_keys":{"x/":"xprv9s21ZrQH143K4D3WqM7zpQrWeqJHJRJhRhpkk5tr2fKBdoTTPDYUL88T12Ad9RHwViugcMbngkMDY626vD5syaFDoUB2cpLeraBaHvZHWFn"},"master_public_keys":{"x/":"xpub661MyMwAqRbcGh7ywNf1BYoFCs8mht2YnvkMYUJTazrAWbnbvkrisvSvrKGjRTDtw324xzprbDgphsmPv2pB6K5Sux3YNHC8pnJANCBY6vG"},"pruned_txo":{},"seed":"tent alien genius panic stage below spoon swap merge hammer gorilla squeeze ability","seed_version":11,"stored_height":489715,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"standard","winpos-qt":[100,100,840,400]}' + wallet_str = '{"accounts":{"0":{"change":["0253e61683b66ebf5a4916334adf1409ffe031016717868c9600d313e87538e745","021762e47578385ecedc03c7055da1713971c82df242920e7079afaf153cc37570","0303a8d6a35956c228aa95a17aab3dee0bca255e8b4f7e8155b23acef15cf4a974","02e881bc60018f9a6c566e2eb081a670f48d89b4a6615466788a4e2ce20246d4c6","02f0090e29817ef64c17f27bf6cdebc1222f7e11d7112073f45708e8d218340777","035b9c53b85fd0c2b434682675ac862bfcc7c5bb6993aee8e542f01d96ff485d67"],"receiving":["024fbc610bd51391794c40a7e04b0e4d4adeb6b0c0cc84ac0b3dad90544e428c47","024a2832afb0a366b149b6a64b648f0df0d28c15caa77f7bbf62881111d6915fe9","028cd24716179906bee99851a9062c6055ec298a3956b74631e30f5239a50cb328","0t3Ryh1RUFbRrifEd1yTwDhqD3Mjristf6jv043c2817f4baca91e7a0c81d164d73d","02606fc2f0ce90edc495a617329b3c5c5cc46e36d36e6c66015b1615137278eabd","02191cc2986e33554e7b155f9eddcc3904fdba43a5a3638499d3b7b5452692b740","024b5bf755b2f65cab1f7e5505febc1db8b91781e5aac352902e79bc96ad7d9ad0","0309816cb047402b84133f4f3c5e56c215e860204513278beef54a87254e44c14a","03f53d34337c12ddb94950b1fee9e4a9cf06ad591db66194871d31a17ec7b59ac7","0325ede4b08073d7f288741c2c577878919fd5d832a9e6e04c9eac5563ae13aa83","02eca43081b04f68d6c8b81781acd59e5b8d2ba44dba195369afc40790fd9edef7","029a8ca96c64d3a98345be1594208908f2be5e6af6bcc6ff3681f271e75fcf232e","02fbe0804980750163a216cc91cfe86e907addf0e80797a8ea5067977eb4897c1b","0344f32fc1ee8b2eb08f419325529f495d77a3b5ea683bbce7a44178705ab59302","021dd62bdf18256bd5316ce3cbcca58785378058a41ba2d1c58f4cc76449b3c424","035e61cdbdb4306e58a816a19ad92c7ca3a392b67ac6d7257646868ffe512068c5","0326a4db82f21787d0246f8144abe6cda124383b7d93a6536f36c05af530ea262a","02b352a27a8f9c57b8e5c89b357ba9d0b5cb18bf623509b34cd881fcf8b89a819a","02a59188edef1ed29c158a0adb970588d2031cfe53e72e83d35b7e8dd0c0c77525","02e8b9e42a54d072c8887542c405f6c99cfabf41bdde639944b44ba7408837afd1"],"xpub":"xpub661MyMwAqRbcGh7ywNf1BYoFCs8mht2YnvkMYUJTazrAWbnbvkrisvSvrKGjRTDtw324xzprbDgphsmPv2pB6K5Sux3YNHC8pnJANCBY6vG"}},"accounts_expanded":{},"addr_history":{"t1KD8ophckVKNgpY5mJWs5SDnh88tpuMnRx":[],"t1Kb8PsbNpjt9wsV55ojECFAjkcpMAGVkQt":[],"t1LcNbQuk1AUckXSbcWrEKkJDcmtXkfwrkb":[],"t1MVtBX25DoyiPMV6nHQCR1PLZjWDdGC7s4":[],"t1MqUJExQqPTvR4Mwa5ig5WT6hq8ZQPiQag":[],"t1NERWgtiqhGz4U362889JibBC4EF5p4PeW":[],"t1QfNvvDBDSrFC2uWrS9c6CaMgoFYw9Q8eS":[],"t1QrQta2GmoTu63XghANZYYim6DRg59PFnk":[],"t1SvD2BcLQM999SeZcQHR5Cwg5tGedR4G34":[],"t1Tkp1732fRwRgvjYuebUBBSb6gLmwiepRG":[],"t1TnGK2u1KvdYByN3FRHPXxzPCUtH7qvkJv":[],"t1TyiE67S7A5DY2T5y2pR7iUXoZGYiu7Deo":[],"t1VwCh52CKYfpz9RMS6ePDQGitA6837taa8":[],"t1WXGMiwmcXDznimYqZZYFWUBRCAqALQBr2":[],"t1Wxi3keaWLEX49zNnWWX93DYXNcoBsweqY":[],"t1Xphi8Bxf8pr1VsK21N3wy6jnJxLXSe8Sg":[],"t1Y9diW2DX15vKeNWkipksibyBxDMNpzRjt":[],"t1ZhLT2iVFB7zs29gVg2N1ka368fmACY5XH":[],"t1ZvntpUQpzcdBdTaDsC7rBDGkNbRzfeBWL":[],"t1af7qMZmNE11kkjr5iDU7FNZBvho9pCVcP":[],"t1dEWewPqjTiPWa8jTUhxbqHcrEojYujWk8":[],"t1dKuKU5oudn2B1TyZe3Sy7z1hnn9Y1zGbY":[],"t1dm8ZFqz33o7QyP3yZwujaRdFN2uwt5fMr":[],"t1fYUV78jAHaVkgmk4h83XHzTLHBzVpqXhU":[],"t1gvDa5QJC7aYgTfVwoB9nZQebNvTc1AvV7":[],"t1JgMpWvwqAag7WEZ1kavkgmcRrRJSxskgX":[]},"master_private_keys":{"x/":"xprv9s21ZrQH143K4D3WqM7zpQrWeqJHJRJhRhpkk5tr2fKBdoTTPDYUL88T12Ad9RHwViugcMbngkMDY626vD5syaFDoUB2cpLeraBaHvZHWFn"},"master_public_keys":{"x/":"xpub661MyMwAqRbcGh7ywNf1BYoFCs8mht2YnvkMYUJTazrAWbnbvkrisvSvrKGjRTDtw324xzprbDgphsmPv2pB6K5Sux3YNHC8pnJANCBY6vG"},"pruned_txo":{},"seed":"tent alien genius panic stage below spoon swap merge hammer gorilla squeeze ability","seed_version":11,"stored_height":489715,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"standard","winpos-qt":[100,100,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_5_4_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":489716,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported","winpos-qt":[595,261,840,400]}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":489716,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported","winpos-qt":[595,261,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_5_4_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":490038,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported","winpos-qt":[406,393,840,400]}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":490038,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported","winpos-qt":[406,393,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_5_4_trezor_singleacc(self): - wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1","029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156","034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e","036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"addr_history":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG"],"pruned_txo":{},"stored_height":490046,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor","winpos-qt":[522,328,840,400]}''' + wallet_str = '''{"accounts":{"0":{"change":["033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80","0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890","038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1","029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156","034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e","036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e"],"receiving":["020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae","03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74","03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046","02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f","031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d","03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717","0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631","035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f","02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be","026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5","0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457","03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429","028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a","03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4","0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482","02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f","02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31","02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1","034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f","0t3KUGeN5k6TMfgH43XzqAAw6zLFJEWApDRi061d5fd1844bc8edc1808d998f8ba2c"],"xpub":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9"}},"accounts_expanded":{},"addr_history":{},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9","x/1'":"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG"},"next_account2":["1","xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG","03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b","t1Rae1B4LfR4LVSAuGMJhnFT3nqfFYaNmHh"],"pruned_txo":{},"stored_height":490046,"transactions":{},"txi":{},"txo":{},"wallet_type":"trezor","winpos-qt":[522,328,840,400]}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_5_4_trezor_multiacc(self): - wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8","03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff","03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7","022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"12bBPWWDwvtXrR9ntSgaQ7AnGyVJr16m5q":[],"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"13853om3ye5c8x6K1LfT3uCWEnG14Z82ML":[],"13BGVmizH8fk3qNm1biNZxAaQY3vPwurjZ":[],"13Tvp2DLQFpUxvc7JxAD3TXfAUWvjhwUiL":[],"15EQcTGzduGXSaRihKy1FY99EQQco8k2UW":[],"15paDwtQ33jJmJhjoBJhpWYGJDFCZppEF9":[],"17X8K766zBYLTjSNvHB9hA6SWRPMTcT556":[],"17zSo4aveNaE5DiTmwNZtxrJmS5ymzvwqj":[],"19BRVkUFfrAcxW9poaBSEUA2yv7SwN3SXh":[],"19gPT2mb9FQCiiPdAmMAaberShzNRiAtTB":[],"1A3vopoUcrWn7JbiAzGZactQz8HbnC1MoD":[],"1D1bn2Jzcx4D2GXbxzrJ1GwP4eNq98Q948":[],"1DvytpRGLJujPtSLYTRABzpy2r6hKJBYQd":[],"1EGg2acXNhJfv1bU3ixrbrmgxFtAUWpdY":[],"1Ev3S9YWxS7KWT8kyLmEuKV5sexNKcMUKV":[],"1FfpRnukxbfBnoudWvw9sdmc86YbVs7eGb":[],"1GBxNE82WLgd38CzoFTEkz6QS9EwLj1ym7":[],"1JFDe97zENNUiKeizcFUHss13vS2AcrVdE":[],"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ":[],"1JQqX3yg6VYxL6unuRArDQaBZYo3ktSCCP":[],"1JUbrr4grE71ZgWNqm9z9ZHHJDcCzFYM4V":[],"1JuHUVbYfBLDUhTHx5tkDDyDbCnMsF8C9w":[],"1KZu7p244ETkdB5turRP4vhG2QJskARYWS":[],"1LE7jioE7y24m3MMZayRKpvdCy2Dz2LQae":[],"1LVr2pTU7LPQu8o8DqsxcGrvwu5rZADxfi":[],"1LmugnVryiuMbgdUAv3LucnRMLvqg8AstU":[],"1MPN5vptDZCXc11fZjpW1pvAgUZ5Ksh3ky":[]},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ"],"pruned_txo":{},"stored_height":490009,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor","winpos-qt":[757,469,840,400]}''' + wallet_str = '''{"accounts":{"0":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"],"xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"1":{"change":["03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34","0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e","036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8","03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff","03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7","022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9"],"receiving":["02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58","039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9","0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec","02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604","0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f","0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6","03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd","03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00","028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3","02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85","03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898","021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4","03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f","0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff","03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7","02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec","0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a","024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6","026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089","02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986","03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129"],"xpub":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH"}},"accounts_expanded":{},"addr_history":{"t1KTnPqvMvFg8T4CgpsVhXvGhXdgPfrbuE5":[],"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[["a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837",490002]],"t1Kzg49BBwxsCjb9CwmUaBiJRVST5peN3M2":[],"t1L3sW798FTTLeURex2XVhmGVfCF17owN86":[],"t1LLXpMdUNac5ZZf1FNyLBGdaR8i1TEjfH6":[],"t1N71cnh8cE483DUcdkn8PMF4V4bhdQRdTc":[],"t1NhBEHJY1NWuMwkdjc7pxKeBYsSHLYY1Zo":[],"t1QPjKSWExWKw4NVGrhzGpyCMm5aSJ1sswL":[],"t1Qs3oQ14chMpfrmMiNBh2mxE26H4d5kaRD":[],"t1S42W5tPeAxDZ9CijzzZNHFxEaJXkFUN5y":[],"t1SYzTNBj7aBoKMSX7CAHiQkmhNBTJ7X34f":[],"t1SvXpADcbBJNhwec7R5giRzLEnUgZBQRto":[],"t1VtCnMj8bGqocuaVuRfR963JKJZurcknPy":[],"t1Woau9qQJdhKzXVEUtEHKovtHWHnC4Uv9e":[],"t1J6sgMzkVhUuGZ4VQUY5zQxgwcSxw7uoMz":[],"t1XneSUxevktv76BeumaN38b18K9T9nAyNP":[],"t1YYRS8KtvvSnPSxXTMkH1SsXNkjgEuqSfQ":[],"t1Z4ZNZYAUfUDdmFtjgGMtoCKgoS2C6LFaK":[],"t1b7peUY8ChA5Jxhcw34bRgxvJad6xKMggn":[],"t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk":[],"t1bHSXPPp4pLYvjxgqqyyMDg6pCz8dEF89C":[],"t1bMCsBUppYtcAKZGnBy7HNPCYsoHkQPfBn":[],"t1bmtUq1gdW7p5LWBtWhsM358qrySf6aFrg":[],"t1cSW89SC2ZFMDp8nrHEWCjoBH4VxXrXYnR":[],"t1d6ik4DN6HofMgQFW1nYTe2YTdDJm3szh5":[],"t1dNT39sc5fB1Vmr2AGh5k5xrCZGwJjk1Z9":[],"t1deWh7uzx3gxCKgN7LrU3RtLc17vSFQg3e":[],"t1eFy6GF2Bsz8Ce4ZWAdd9e25w8kA6ZXWdZ":[]},"labels":{"0":"Main account"},"master_public_keys":{"x/0'":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y","x/1'":"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH","x/2'":"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa"},"next_account2":["2","xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa","031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e","t1b9FkNbGZNAUeT63tSvXStahWYZsLse5nk"],"pruned_txo":{},"stored_height":490009,"transactions":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000"},"txi":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{}},"txo":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":{"t1Ko7QKj2Ef72ANqSSKKa3UqMATRDapjwdE":[[0,5000,false]]}},"verified_tx3":{"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837":[490002,1508090436,607]},"wallet_type":"trezor","winpos-qt":[757,469,840,400]}''' self._upgrade_storage(wallet_str, accounts=2) def test_upgrade_from_client_2_5_4_multisig(self): - wallet_str = '{"accounts":{"0":{"change":[["02a63209b49df0bb98d8a262e9891fe266ffdce4be09d5e1ffaf269a10d7e7a17c","02a074035006ed8ee8f200859c004c073b687140f7d40bd333cdbbe43bad1e50bc"],["0280e2367142669e08e27fb9fd476076a7f34f596e130af761aef54ec54954a64d","02719a66c59f76c36921cf7b330fca7aaa4d863ee367828e7d89cd2f1aad98c3ac"],["0332083e80df509d3bd8a06538ca20030086c9ed3313300f7313ed98421482020f","032f336744f53843d8a007990fa909e35e42e1e32460fae2e0fc1aef7c2cff2180"],["03fe014e5816497f9e27d26ce3ae8d374edadec410227b2351e9e65eb4c5d32ab7","0226edd8c3af9e339631145fd8a9f6d321fdc52fe0dc8e30503541c348399dd52a"],["03e6717b18d7cbe264c6f5d0ad80f915163f6f6c08c121ac144a7664b95aedfdf3","03d69a074eba3bc2c1c7b1f6f85822be39aee20341923e406c2b445c255545394a"],["023112f87a5b9b2eadc73b8d5657c137b50609cd83f128d130172a0ed9e3fea9bc","029a81fd5ba57a2c2c6cfbcb34f369d87af8759b66364d5411eddd28e8a65f67fa"]],"m":2,"receiving":[["03c35c3da2c864ee3192a847ffd3f67fa59c095d8c2c0f182ed9556308ec37231e","03cfcb6d1774bfd916bd261232645f6c765da3401bf794ab74e84a6931d8318786"],["03973c83f84a4cf5d7b21d1e8b29d6cbd4cb40d7460166835cd1e1fd2418cfcf2e","03596801e66976959ac1bdb4025d65a412d95d320ed9d1280ac3e89b041e663cf4"],["02b78ac89bfdf90559f24313d7393af272092827efc33ba3a0d716ee8b75fd08ff","038e21fae8a033459e15a700551c1980131eb555bbb8b23774f8851aa10dcac6b8"],["0288e9695bb24f336421d5dcf16efb799e7d1f8284413fe08e9569588bc116567e","027123ba3314f77a8eb8bb57ba1015dd6d61b709420f6a3320ba4571b728ef2d91"],["0312e1483f7f558aef1a14728cc125bb4ee5cff0e7fa916ba8edd25e3ebceb05e9","02dad92a9893ad95d3be5ebc40828cef080e4317e3a47af732127c3fee41451356"],["03a694e428a74d37194edc9e231e68399767fdb38a20eca7b72caf81b7414916a8","03129a0cef4ed428031972050f00682974b3d9f30a571dc3917377595923ac41d8"],["026ed41491a6d0fb3507f3ca7de7fb2fbfdfb28463ae2b91f2ab782830d8d5b32c","03211b3c30c41d54734b3f13b8c9354dac238d82d012839ee0199b2493d7e7b6fc"],["03480e87ffa55a96596be0af1d97bca86987741eb5809675952a854d59f5e8adc2","0215f04df467d411e2a9ed8883a21860071ab721314503019a10ed30e225e522e7"],["0389fce63841e9231d5890b1a0c19479f8f40f4f463ef8e54ef306641abe545ac8","02396961d498c2dcb3c7081b50c5a4df15fda31300285a4c779a59c9abc98ea20d"],["03d4a3053e9e08dc21a334106b5f7d9ac93e42c9251ceb136b83f1a614925eb1fb","025533963c22b4f5fbfe75e6ee5ad7ee1c7bff113155a7695a408049e0b16f1c52"],["038a07c8d2024b9118651474bd881527e8b9eb85fc90fdcb04c1e38688d498de4b","03164b188eb06a3ea96039047d0db1c8f9be34bfd454e35471b1c2f429acd40afb"],["0214070cd393f39c062ce1e982a8225e5548dbbbd654aeba6d36bfcc7a685c7b12","029c6a9fb61705cc39bef34b09c684a362d4862b16a3b0b39ca4f94d75cd72290c"],["027b3497f72f581fea0a678bc20482b6fc7b4b507f7263d588001d73fdf5fe314e","021b80b159d19b6978a41c2a6bf7d3448bc73001885f933f7854f450b5873091f3"],["0303e9d76e4fe7336397c760f6fdfd5fb7500f83e491efb604fa2442db6e1da417","03a8d1b22a73d4c181aecd8cfe8bb2ee30c5dd386249d2a5a3b071b7a25b9da73a"],["0298e472b74832af856fb68eed02ff00a235fd0424d833bc305613e9f44087d0ee","03bb9bc2e4aaa9b022b35c8d122dfccb6c28ae8f0996a8fb4a021af8ec96a7beaf"],["02e933a4afb354500da03373514247e1be12e67cc4683e0cb82f508878cc3cc048","02c07a57b071bc449a95dd80308e53b26e4ebf4d523f620eecb17f96ae3aa814e9"],["03f73476951078b3ccc549bc7e6362797aaaacb1ea0edc81404b4d16cb321255a3","03b3a825fb9fc497e568fba69f70e2c3dcdc793637e242fce578546fcbd33cb312"],["03bbdf99fddeea64a96bbb9d1e6d7ced571c9c7757045dcbd8c40137125b017dc5","03aedf4452afefb1c3da25e698f621cb3a3a0130aa299488e018b93a45b5e6c21d"],["03b85891edb147d43c0a5935a20d6bbf8d32c542bfecccf3ae0158b65bd639b34e","03b34713c636a1c103b82d6cec917d442c59522ddc5a60bf7412266dd9790e7760"],["028ddf53b85f6c01122a96bd6c181ee17ca222ee9eca85bdeeb25c4b5315005e3b","02f4821995bfd5d0adb7a78d6e3a967ac72ace9d9a4f9392aff2711533893e017b"]],"xpubs":["xpub661MyMwAqRbcGHtCYBSGGVgMSihroMkuyE25GPyzfQvS2vSFG7SgJYf7rtXJjMh7srBJj8WddLtjapHnUQLwJ7kxsy5HiNZnGvF9pm2du7b","xpub661MyMwAqRbcEdd7bzA86LbhMoTv8NeyqcNP5z1Tiz9ajCRQDzdeXHw3h5ucDNGWr6mPFCZBcBE31VNKyR3vWM7WEeisu5m4VsCyuA6H8fp"]}},"accounts_expanded":{},"addr_history":{"32JvbwfEGJwZHGm3nwYiXyfsnGCb3L8hMX":[],"32pWy5sKkQsjyDz45tog47cA8vQyzC3UUZ":[],"334yqX1WtS6mY2vize7znTaL64HspwVkGF":[],"33GY9w6a4XmLAWxNgNFFRXTTRxbu3Nz8ip":[],"33geBcyW8Bw53EgAv3qwMVkVnvxZWj5J1X":[],"35BneogkCNxSiSN1YLmhKLP8giDbGkZiTX":[],"37U4J5b9B7rQnQXYstMoQnb6i9aWpptnLi":[],"37gqbHdbrCcGyrNF21AiDkofVCie5LpFmQ":[],"37t1Q5R92co4by2aagtLcqdWTDEzFuAuwZ":[],"37z3ruAHCxnzeJeLz96ZpkbwS3CLbtXtPc":[],"39qePsKaeviFEMC6CWX37DqaQda4jA2E6A":[],"3A5eratrDWu4SqsoHpuqswNsQmp9k8TXR2":[],"3B1N3PG5dNPYsTAuHFbVfkwXeZqqNS1CuP":[],"3BABbvd3eAuwiqJwppm54dJauKnRUieQU8":[],"3CAsH7BJnNT4kmwrbG8XZMMwW6ue8w4auJ":[],"3CX2GLCTfpFHSgAmbGRmuDKGHMbWY8tCp7":[],"3CrLUTVHuG1Y3swny9YDmkfJ89iHHU93NB":[],"3CxRa6yAQ2N2rpDHyUTaViGG4XVASAqwAN":[],"3DLTrsdYabso7QpxoLSW5ZFjLxBwrLEqqW":[],"3GG3APgrdDCTmC9tTwWu3sNV9aAnpFcddA":[],"3JDWpTxnsKoKut9WdG4k933qmPE5iJ8hRR":[],"3LdHoahj7rHRrQVe38D4iN43ySBpW5HQRZ":[],"3Lt56BqiJwZ1um1FtXJXzbY5uk32GVBa8K":[],"3MM9417myjN7ubMDkaK1wQ9RbjEc1zHCRH":[],"3NTivFVXva4DCjPmsf5p5Gt1dmuV39qD2v":[],"3QCwtjMywMtT3Vg6BwS146LcQjJnZPAPHZ":[]},"master_private_keys":{"x1/":"xprv9s21ZrQH143K29YeVxd7jCexomdRiuw8UPSnHbbrAecbrQ6FgTKPyVcZqp2256L5DSTdb8UepPVaDwJecswTrEhdyZiaNGERJpfzWV5FcN5"},"master_public_keys":{"x1/":"xpub661MyMwAqRbcEdd7bzA86LbhMoTv8NeyqcNP5z1Tiz9ajCRQDzdeXHw3h5ucDNGWr6mPFCZBcBE31VNKyR3vWM7WEeisu5m4VsCyuA6H8fp","x2/":"xpub661MyMwAqRbcGHtCYBSGGVgMSihroMkuyE25GPyzfQvS2vSFG7SgJYf7rtXJjMh7srBJj8WddLtjapHnUQLwJ7kxsy5HiNZnGvF9pm2du7b"},"pruned_txo":{},"seed":"park dash merit trend life field acid wrap dinosaur kit bar hotel abuse","seed_version":11,"stored_height":490034,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"2of2","winpos-qt":[564,329,840,400]}' + wallet_str = '{"accounts":{"0":{"change":[["02a63209b49df0bb98d8a262e9891fe266ffdce4be09d5e1ffaf269a10d7e7a17c","02a074035006ed8ee8f200859c004c073b687140f7d40bd333cdbbe43bad1e50bc"],["0280e2367142669e08e27fb9fd476076a7f34f596e130af761aef54ec54954a64d","02719a66c59f76c36921cf7b330fca7aaa4d863ee367828e7d89cd2f1aad98c3ac"],["0332083e80df509d3bd8a06538ca20030086c9ed3313300f7313ed98421482020f","032f336744f53843d8a007990fa909e35e42e1e32460fae2e0fc1aef7c2cff2180"],["03fe014e5816497f9e27d26ce3ae8d374edadec410227b2351e9e65eb4c5d32ab7","0226edd8c3af9e339631145fd8a9f6d321fdc52fe0dc8e30503541c348399dd52a"],["03e6717b18d7cbe264c6f5d0ad80f915163f6f6c08c121ac144a7664b95aedfdf3","03d69a074eba3bc2c1c7b1f6f85822be39aee20341923e406c2b445c255545394a"],["023112f87a5b9b2eadc73b8d5657c137b50609cd83f128d130172a0ed9e3fea9bc","029a81fd5ba57a2c2c6cfbcb34f369d87af8759b66364d5411eddd28e8a65f67fa"]],"m":2,"receiving":[["03c35c3da2c864ee3192a847ffd3f67fa59c095d8c2c0f182ed9556308ec37231e","03cfcb6d1774bfd916bd261232645f6c765da3401bf794ab74e84a6931d8318786"],["03973c83f84a4cf5d7b21d1e8b29d6cbd4cb40d7460166835cd1e1fd2418cfcf2e","03596801e66976959ac1bdb4025d65a412d95d320ed9d1280ac3e89b041e663cf4"],["02b78ac89bfdf90559f24313d7393af272092827efc33ba3a0d716ee8b75fd08ff","038e21fae8a033459e15a700551c1980131eb555bbb8b23774f8851aa10dcac6b8"],["0288e9695bb24f336421d5dcf16efb799e7d1f8284413fe08e9569588bc116567e","027123ba3314f77a8eb8bb57ba1015dd6d61b709420f6a3320ba4571b728ef2d91"],["0312e1483f7f558aef1a14728cc125bb4ee5cff0e7fa916ba8edd25e3ebceb05e9","02dad92a9893ad95d3be5ebc40828cef080e4317e3a47af732127c3fee41451356"],["03a694e428a74d37194edc9e231e68399767fdb38a20eca7b72caf81b7414916a8","03129a0cef4ed428031972050f00682974b3d9f30a571dc3917377595923ac41d8"],["026ed41491a6d0fb3507f3ca7de7fb2fbfdfb28463ae2b91f2ab782830d8d5b32c","03211b3c30c41d54734b3f13b8c9354dac238d82d012839ee0199b2493d7e7b6fc"],["03480e87ffa55a96596be0af1d97bca86987741eb5809675952a854d59f5e8adc2","0215f04df467d411e2a9ed8883a21860071ab721314503019a10ed30e225e522e7"],["0389fce63841e9231d5890b1a0c19479f8f40f4f463ef8e54ef306641abe545ac8","02396961d498c2dcb3c7081b50c5a4df15fda31300285a4c779a59c9abc98ea20d"],["03d4a3053e9e08dc21a334106b5f7d9ac93e42c9251ceb136b83f1a614925eb1fb","025533963c22b4f5fbfe75e6ee5ad7ee1c7bff113155a7695a408049e0b16f1c52"],["038a07c8d2024b9118651474bd881527e8b9eb85fc90fdcb04c1e38688d498de4b","03164b188eb06a3ea96039047d0db1c8f9be34bfd454e35471b1c2f429acd40afb"],["0214070cd393f39c062ce1e982a8225e5548dbbbd654aeba6d36bfcc7a685c7b12","029c6a9fb61705cc39bef34b09c684a362d4862b16a3b0b39ca4f94d75cd72290c"],["027b3497f72f581fea0a678bc20482b6fc7b4b507f7263d588001d73fdf5fe314e","021b80b159d19b6978a41c2a6bf7d3448bc73001885f933f7854f450b5873091f3"],["0303e9d76e4fe7336397c760f6fdfd5fb7500f83e491efb604fa2442db6e1da417","03a8d1b22a73d4c181aecd8cfe8bb2ee30c5dd386249d2a5a3b071b7a25b9da73a"],["0298e472b74832af856fb68eed02ff00a235fd0424d833bc305613e9f44087d0ee","03bb9bc2e4aaa9b022b35c8d122dfccb6c28ae8f0996a8fb4a021af8ec96a7beaf"],["02e933a4afb354500da03373514247e1be12e67cc4683e0cb82f508878cc3cc048","02c07a57b071bc449a95dd80308e53b26e4ebf4d523f620eecb17f96ae3aa814e9"],["03f73476951078b3ccc549bc7e6362797aaaacb1ea0edc81404b4d16cb321255a3","03b3a825fb9fc497e568fba69f70e2c3dcdc793637e242fce578546fcbd33cb312"],["03bbdf99fddeea64a96bbb9d1e6d7ced571c9c7757045dcbd8c40137125b017dc5","03aedf4452afefb1c3da25e698f621cb3a3a0130aa299488e018b93a45b5e6c21d"],["03b85891edb147d43c0a5935a20d6bbf8d32c542bfecccf3ae0158b65bd639b34e","03b34713c636a1c103b82d6cec917d442c59522ddc5a60bf7412266dd9790e7760"],["028ddf53b85f6c01122a96bd6c181ee17ca222ee9eca85bdeeb25c4b5315005e3b","02f4821995bfd5d0adb7a78d6e3a967ac72ace9d9a4f9392aff2711533893e017b"]],"xpubs":["xpub661MyMwAqRbcGHtCYBSGGVgMSihroMkuyE25GPyzfQvS2vSFG7SgJYf7rtXJjMh7srBJj8WddLtjapHnUQLwJ7kxsy5HiNZnGvF9pm2du7b","xpub661MyMwAqRbcEdd7bzA86LbhMoTv8NeyqcNP5z1Tiz9ajCRQDzdeXHw3h5ucDNGWr6mPFCZBcBE31VNKyR3vWM7WEeisu5m4VsCyuA6H8fp"]}},"accounts_expanded":{},"addr_history":{"t3KBXcH5NEdj9suowjNMqfnmo2vPfn7mFhR":[],"t3Kh7yRHTijfLZs2x2KcoBvi5Pac4rCRRqD":[],"t3KwaqrRerktN8fycw4w7vGgFLiUxc5T3GA":[],"t3L99AGWi2rYvmA1Gco4NZLZNgcnyo6rdsj":[],"t3LZFBxPe6Wifdsj4rUf4VJrR3b9eFE2KNi":[],"t3N4Pf96tAhk3K5QuUmapT9V3wNQg3AGTv6":[],"t3QLfJR1H9Se1P3aSpKAvYbh1xombhnETh2":[],"t3QZSbd3jpXPsaVR8xRyqMZuajruiwngjcJ":[],"t3QkcQQqGzwafCc5UX7hTkejRhsS53j2REm":[],"t3QresEaRBHabEwhEvZugxZhrghPRL9MHiV":[],"t3SiFQCjidFVqpzEz8wLAF2wVfHm9VnDKBW":[],"t3SxFrvJzBqgf3UvhEFiy1kUnfS1EYAcS94":[],"t3Tsy3igDbhB9U6DoDgQcoa3SuE2vAD3pHb":[],"t3U2ncG3BcVhYKUMqmFaCCSQW9yyWJQ9WKh":[],"t3V3UHSbSkhEfMQzkXgwehATrkm6itmF3dJ":[],"t3VPdGfcbe92t3KDfXhEu32RBY1nbLxRzmW":[],"t3ViwUnuRsao8eWzguaMLuZmDNouN5BNU6p":[],"t3Vq2aSPJNM9dTTGBuuGhdXNBKBgFJQBdPg":[],"t3WD4sD3gYvfPi3srjmFdDNMebcP2fC73RS":[],"t3Z8eAj6zbXz4MqCnQNL2BgUQQEMsdQVfYH":[],"t3b67poNvqeavWXCQZgssGr9m23RAVZnRYM":[],"t3dVtov7s6B52T3YXyZ2BrB9yE6NuHn9gDi":[],"t3dkg6XFrHGLcWQ49px7f8Qe1AQE746JRGK":[],"t3eDk4LXux49iWEQ7h1895DFLrPRgt8aK8d":[],"t3fLKvaufttqooNSfp5twD5yvtS6Znh5SYc":[],"t3h5Yu4n7ugg3e8iz8NF8BuSXfPVsHuxZKV":[]},"master_private_keys":{"x1/":"xprv9s21ZrQH143K29YeVxd7jCexomdRiuw8UPSnHbbrAecbrQ6FgTKPyVcZqp2256L5DSTdb8UepPVaDwJecswTrEhdyZiaNGERJpfzWV5FcN5"},"master_public_keys":{"x1/":"xpub661MyMwAqRbcEdd7bzA86LbhMoTv8NeyqcNP5z1Tiz9ajCRQDzdeXHw3h5ucDNGWr6mPFCZBcBE31VNKyR3vWM7WEeisu5m4VsCyuA6H8fp","x2/":"xpub661MyMwAqRbcGHtCYBSGGVgMSihroMkuyE25GPyzfQvS2vSFG7SgJYf7rtXJjMh7srBJj8WddLtjapHnUQLwJ7kxsy5HiNZnGvF9pm2du7b"},"pruned_txo":{},"seed":"park dash merit trend life field acid wrap dinosaur kit bar hotel abuse","seed_version":11,"stored_height":490034,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"2of2","winpos-qt":[564,329,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_6_4_seeded(self): - wallet_str = '{"accounts":{"0":{"change":["03236a8ce6fd3d343358f92d3686b33fd6e7301bf9f635e94c21825780ab79c93d","0393e39f6b4a3651013fca3352b89f1ae31751d4268603f1423c71ff79cbb453a1","033d9722ecf50846527037295736708b20857b4dd7032fc02317f9780d6715e8ff","03f1d56d2ade1daae5706ea945cab2af719060a955c8ad78153693d8d08ed6b456","029260d935322dd3188c3c6b03a7b82e174f11ca7b4d332521740c842c34649137","0266e8431b49f129b892273ab4c8834a19c6432d5ed0a72f6e88be8c629c731ede"],"receiving":["0350f41cfac3fa92310bb4f36e4c9d45ec39f227a0c6e7555748dff17e7a127f67","02f997d3ed0e460961cdfa91dec4fa09f6a7217b2b14c91ed71d208375914782ba","029a498e2457744c02f4786ac5f0887619505c1dae99de24cf500407089d523414","03b15b06044de7935a0c1486566f0459f5e66c627b57d2cda14b418e8b9017aca1","026e9c73bdf2160630720baa3da2611b6e34044ad52519614d264fbf4adc5c229a","0205184703b5a8df9ae622ea0e8326134cbeb92e1f252698bc617c9598aff395a1","02af55f9af0e46631cb7fde6d1df6715dc6018df51c2370932507e3d6d41c19eec","0374e0c89aa4ecf1816f374f6de8750b9c6648d67fe0316a887a132c608af5e7c0","0321bb62f5b5c393aa82750c5512703e39f4824f4c487d1dc130f690360c0e5847","0338ea6ebb2ed80445f64b2094b290c81d0e085e6000367eb64b1dc5049f11c2e9","020c3371a9fd283977699c44a205621dea8abfc8ebc52692a590c60e22202fa49b","0395555e4646f94b10af7d9bc57e1816895ad2deddef9d93242d6d342cea3d753b","02ffa4495d020d17b54da83eaf8fbe489d81995577021ade3a340a39f5a0e2d45c","030f0e16b2d55c3b40b64835f87ab923d58bcdbb1195fadc2f05b6714d9331e837","02f70041fc4b1155785784a7c23f35d5d6490e300a7dd5b7053f88135fc1f14dfd","03b39508c6f9c7b8c3fb8a1b91e61a0850c3ac76ccd1a53fbc5b853a94979cffa8","03b02aa869aa14b0ec03c4935cc12f221c3f204f44d64146d468e07370c040bfe7","02b7d246a721e150aaf0e0e60a30ad562a32ef76a450101f3f772fef4d92b212d9","037cd5271b31466a75321d7c9e16f995fd0a2b320989c14bee82e161c83c714321","03d4ad77e15be312b29987630734d27ca6e9ee418faa6a8d6a50581eca40662829"],"xpub":"xpub661MyMwAqRbcGwHDovebbFy19vHfW2Cqtyf2TaJkAwhFWsLYfHHYcCnM7smpvntxJP1YMVT5triFbWiCGXPRPhqdCxFumA77MuQB1CeWHpE"}},"accounts_expanded":{},"addr_history":{"12qKnKuhCZ1Q9XBi1N6SnxYEUtb5XZXuY5":[],"1321ddunxShHmF4cjh3v5yqR7uatvSNndK":[],"13Ji3kGWn9qxLcWGhd46xjV6hg8SRw8x2P":[],"145q5ZDXuFi6v9dA2t8HyD8ysorfb81NRt":[],"14gB2wLy2DMkBVtuU6HHP3kQYNFYPzAguU":[],"16VGRwtZwp4yapQN5fS8CprK6mmnEicCEj":[],"16ahKVzCviRi24rwkoKgiSVSkvRNiQudE1":[],"16wjKZ1CWAMEzSR4UxQTWqXRm9jcJ9Dbuf":[],"18ReWGJBq1XkJaPAirVdT6RqDskcFeD5Ho":[],"1A1ECMMJU4NicWNwfMBn3XJriB4WHAcPUC":[],"1Bvxbfc2wXB8z8kyz2uyKw2Ps8JeGQM9FP":[],"1EDWUz4kPq8ZbCdQq8rLhFc3qSZ6Fpt1TD":[],"1EsvTarawMm5BfF44hpRtE4GfZFfZZ1JG3":[],"1JgaekD2ETMJm6oRNnwTWRK9ZxXeUcbi18":[],"1KHdLodsSWj1LrrD9d1RbApfqzpxRs5sxu":[],"1KgGwpKhruHWpMNtrpRExDWLLk5qHCHBdg":[],"1LFf8d3XD9atZvMVMAiq9ygaeZbphbKzSo":[],"1N3XncDQsWE2qff1EVyQEmR6JLLzD3mEL7":[],"1NUtLcVQNmY5TJCieM1cUmBmv18AafY1vq":[],"1NYFsm7PpneT65byRtm8niyvtzKsbEeuXA":[],"1NvEcSvfCe8LPvPkK4ZxhjzaUncTPqe9jX":[],"1PV8xdkYKxeMpnzeeA4eYEpL24j1G9ApV2":[],"1PdiGtznaW1mok6ETffeRvPP5f4ekBRAfq":[],"1QApNe4DtK7HAbJrn5kYkYxZMt86U5ChSb":[],"1QnH7F6RBXFe7LtszQ6KTRUPkQKRtXTnm":[],"1ekukhMNSWCfnRsmpkuTRuLMbz6cstkrq":[]},"master_private_keys":{"x/":"xprv9s21ZrQH143K4TCkhu7bE82GbtTB6ZUzXkjRfBu8ccAGe51Q7jyJ4QTsGbWxpHxnatKeYV7Ad83m7KC81THBm2xmyxA1q8BuuRXSGnmhhR8"},"master_public_keys":{"x/":"xpub661MyMwAqRbcGwHDovebbFy19vHfW2Cqtyf2TaJkAwhFWsLYfHHYcCnM7smpvntxJP1YMVT5triFbWiCGXPRPhqdCxFumA77MuQB1CeWHpE"},"pruned_txo":{},"seed":"heart cabbage scout rely square census satoshi home purpose legal replace move able","seed_version":11,"stored_height":489716,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"standard","winpos-qt":[582,394,840,400]}' + wallet_str = '{"accounts":{"0":{"change":["03236a8ce6fd3d343358f92d3686b33fd6e7301bf9f635e94c21825780ab79c93d","0393e39f6b4a3651013fca3352b89f1ae31751d4268603f1423c71ff79cbb453a1","033d9722ecf50846527037295736708b20857b4dd7032fc02317f9780d6715e8ff","03f1d56d2ade1daae5706ea945cab2af719060a955c8ad78153693d8d08ed6b456","029260d935322dd3188c3c6b03a7b82e174f11ca7b4d332521740c842c34649137","0266e8431b49f129b892273ab4c8834a19c6432d5ed0a72f6e88be8c629c731ede"],"receiving":["0350f41cfac3fa92310bb4f36e4c9d45ec39f227a0c6e7555748dff17e7a127f67","02f997d3ed0e460961cdfa91dec4fa09f6a7217b2b14c91ed71d208375914782ba","029a498e2457744c02f4786ac5f0887619505c1dae99de24cf500407089d523414","03b15b06044de7935a0c1486566f0459f5e66c627b57d2cda14b418e8b9017aca1","026e9c73bdf2160630720baa3da2611b6e34044ad52519614d264fbf4adc5c229a","0205184703b5a8df9ae622ea0e8326134cbeb92e1f252698bc617c9598aff395a1","02af55f9af0e46631cb7fde6d1df6715dc6018df51c2370932507e3d6d41c19eec","0374e0c89aa4ecf1816f374f6de8750b9c6648d67fe0316a887a132c608af5e7c0","0321bb62f5b5c393aa82750c5512703e39f4824f4c487d1dc130f690360c0e5847","0338ea6ebb2ed80445f64b2094b290c81d0e085e6000367eb64b1dc5049f11c2e9","020c3371a9fd283977699c44a205621dea8abfc8ebc52692a590c60e22202fa49b","0395555e4646f94b10af7d9bc57e1816895ad2deddef9d93242d6d342cea3d753b","02ffa4495d020d17b54da83eaf8fbe489d81995577021ade3a340a39f5a0e2d45c","030f0e16b2d55c3b40b64835f87ab923d58bcdbb1195fadc2f05b6714d9331e837","02f70041fc4b1155785784a7c23f35d5d6490e300a7dd5b7053f88135fc1f14dfd","03b39508c6f9c7b8c3fb8a1b91e61a0850c3ac76ccd1a53fbc5b853a94979cffa8","03b02aa869aa14b0ec03c4935cc12f221c3f204f44d64146d468e07370c040bfe7","02b7d246a721e150aaf0e0e60a30ad562a32ef76a450101f3f772fef4d92b212d9","037cd5271b31466a75321d7c9e16f995fd0a2b320989c14bee82e161c83c714321","03d4ad77e15be312b29987630734d27ca6e9ee418faa6a8d6a50581eca40662829"],"xpub":"xpub661MyMwAqRbcGwHDovebbFy19vHfW2Cqtyf2TaJkAwhFWsLYfHHYcCnM7smpvntxJP1YMVT5triFbWiCGXPRPhqdCxFumA77MuQB1CeWHpE"}},"accounts_expanded":{},"addr_history":{"t1KhvnfKqAsnzkAEbwnuZvme9jYnAMviXDB":[],"t1KtcdyKvvmUtMt7Wg7s3DnwLNZmyf4aZMx":[],"t1LBK45gekUdYwFZAe3sE6Yb1xLKXJ9hZd7":[],"t1LxS5tdfsaVhWng3yJwR72Eu8U3kHnTZLx":[],"t1MYn3Gm6zY9Ln8woQX6QWrrKo2SdD8ad6T":[],"t1PMsSHJhv8raBTTG26FFLdxEMRxs1edEvT":[],"t1PTJKqQLu3DJchuqhE8orFbN1acTWkyUWQ":[],"t1PpLKtRLUV8qb5TxRPDaeedM1ovh6R7qbi":[],"t1RJFWbiKoLKLuDS4fHJkauXkUXwgz9soXd":[],"t1SsqCgmSSPAKD9RqbmzuBLQmxqFb17XafQ":[],"t1UoZc12AuqxjamosvTj6Tk8K7nVj1mroWM":[],"t1X67VKUtN9vABqgJmZfTq4hy66kB5A3PLN":[],"t1XkXTvGiugYfnJHx18dZ23ABvDSkJ2fGBN":[],"t1bZBf5dACn8uMjrKKDkaeER4pcijJhNGhE":[],"t1cAEM941QqWbwVu763pYiyvb6f23GnEtr7":[],"t1cYsx9jqqE57QzRnoFEN62cFbQGv9qpP8Z":[],"t1d8G8xTfBUNVAZQPHbXxHnnVuDnuVqoGjF":[],"t1ev8nwdYqq1dSJhuAvnXNaX1YzY4yVGqwG":[],"t1fMVLwuYM6Kg3wFcampjcaHhAfKFQkT1x2":[],"t1fQrt6XXo7S3giesNKaFvY5r9eWxMnGp4Y":[],"t1fnqcnLoAxuvzZSeFVP5qZ6VjSoYCxRNAQ":[],"t1gMjxyAgJHRxRS3Yaasmg3vFGiv5zQPUvp":[],"t1gWKHEQvYpoNQP98Q6UmZjVJLKFjXEWhB7":[],"t1h3RNyUMrdtsmEMkiWZftN4UcYKBL1dVFT":[],"t1JHPHSfEPWJrEkPnpRDDTGXPeQbQ8nyjKs":[],"t1JXMv67VLmHoGRUmiFa2bF1FcGBBMptzBk":[]},"master_private_keys":{"x/":"xprv9s21ZrQH143K4TCkhu7bE82GbtTB6ZUzXkjRfBu8ccAGe51Q7jyJ4QTsGbWxpHxnatKeYV7Ad83m7KC81THBm2xmyxA1q8BuuRXSGnmhhR8"},"master_public_keys":{"x/":"xpub661MyMwAqRbcGwHDovebbFy19vHfW2Cqtyf2TaJkAwhFWsLYfHHYcCnM7smpvntxJP1YMVT5triFbWiCGXPRPhqdCxFumA77MuQB1CeWHpE"},"pruned_txo":{},"seed":"heart cabbage scout rely square census satoshi home purpose legal replace move able","seed_version":11,"stored_height":489716,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"standard","winpos-qt":[582,394,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_6_4_importedkeys(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":489716,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported","winpos-qt":[510,338,840,400]}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":489716,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"imported","winpos-qt":[510,338,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_6_4_watchaddresses(self): - wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":490038,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported","winpos-qt":[582,425,840,400]}' + wallet_str = '{"accounts":{"/x":{"imported":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[null,null],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[null,null],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[null,null]}}},"accounts_expanded":{},"addr_history":{},"pruned_txo":{},"stored_height":490038,"transactions":{},"txi":{},"txo":{},"wallet_type":"imported","winpos-qt":[582,425,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_6_4_multisig(self): - wallet_str = '{"accounts":{"0":{"change":[["03d0bcdc86a64cc2024c84853e88985f6f30d3dc3f219b432680c338a3996a89ed","024f326d48aa0a62310590b10522b69d250a2439544aa4dc496f7ba6351e6ebbfe"],["03c0416928528a9aaaee558590447ee63fd33fa497deebefcf363b1af90d867762","03db7de16cd6f3dcd0329a088382652bc3e6b21ee1a732dd9655e192c887ed88a7"],["0291790656844c9d9c24daa344c0b426089eadd3952935c58ce6efe00ef1369828","02c2a5493893643102f77f91cba709f11aaab3e247863311d6fc3d3fc82624c3cc"],["023dc976bd1410a7e9f34c230051db58a3f487763f00df1f529b10f55ee85b931c","036c318a7530eedf3584fd8b24c4024656508e35057a0e7654f21e89e121d0bd30"],["02c8820711b39272e9730a1c5c5c78fe39a642b8097f8724b2592cc987017680ce","0380e3ebe0ea075e33acb3f796ad6548fde86d37c62fe8e4f6ab5d2073c1bb1d43"],["0369a32ddd213677a0509c85af514537d5ee04c68114da3bc720faeb3adb45e6f8","0370e85ac01af5e3fd5a5c3969c8bca3e4fc24efb9f82d34d5790e718a507cecb6"]],"m":2,"receiving":[["0207739a9ff4a643e1d4adb03736ec43d13ec897bdff76b40a25d3a16e19e464aa","02372ea4a291aeb1fadb26f36976348fc169fc70514797e53b789a87c9b27cc568"],["0248ae7671882ec87dd6bacf7eb2ff078558456cf5753952cddb5dde08f471f3d6","035bac54828b383545d7b70824a8be2f2d9584f656bfdc680298a38e9383ed9e51"],["02cb99ba41dfbd510cd25491c12bd0875fe8155b5a6694ab781b42bd949252ff26","03b520feba42149947f8b2bbc7e8c03f9376521f20ac7b7f122dd44ab27309d7c6"],["0395902d5ebb4905edd7c4aedecf17be0675a2ffeb27d85af25451659c05cc5198","02b4a01d4bd25cadcbf49900005e8d5060ed9cdc35eb33f2cd65cc45cc7ebc00c5"],["02f9d06c136f05acc94e4572399f17238bb56fa15271e3cb816ae7bb9be24b00b6","035516437612574b2b563929c49308911651205e7cebb621940742e570518f1c50"],["0376a7de3abaee6631bd4441658987c27e0c7eee2190a86d44841ae718a014ee43","03cb702364ffd59cb92b2e2128c18d8a5a255be2b95eb950641c5f17a5a900eecb"],["03240c5e868ecb02c4879ae5f5bad809439fdbd2825769d75be188e34f6e533a67","026b0d05784e4b4c8193443ce60bea162eee4d99f9dfa94a53ae3bc046a8574eeb"],["02d087cccb7dc457074aa9decc04de5a080757493c6aa12fa5d7d3d389cfdb5b8e","0293ab7d0d8bbb2d433e7521a1100a08d75a32a02be941f731d5809b22d86edb33"],["03d1b83ab13c5b35701129bed42c1f1fbe86dd503181ad66af3f4fb729f46a277e","0382ec5e920bc5c60afa6775952760668af42b67d36d369cd0e9acc17e6d0a930d"],["03f1737db45f3a42aebd813776f179d5724fce9985e715feb54d836020b8517bfe","0287a9dfb8ee2adab81ef98d52acd27c25f558d2a888539f7d583ef8c00c34d6dc"],["038eb8804e433023324c1d439cd5fbbd641ca85eadcfc5a8b038cb833a755dac21","0361a7c80f0d9483c416bc63d62506c3c8d34f6233b6d100bb43b6fe8ec39388b9"],["0336437ada4cd35bec65469afce298fe49e846085949d93ef59bf77e1a1d804e4a","0321898ed89df11fcfb1be44bb326e4bb3272464f000a9e51fb21d25548619d377"],["0260f0e59d6a80c49314d5b5b857d1df64d474aba48a37c95322292786397f3dc6","03acd6c9aeac54c9510304c2c97b7e206bbf5320c1e268a2757d400356a30c627b"],["0373dc423d6ee57fac3b9de5e2b87cf36c21f2469f17f32f5496e9e7454598ba8e","031ddc1f40c8b8bf68117e790e2d18675b57166e9521dff1da44ba368be76555b3"],["031878b39bc6e35b33ceac396b429babd02d15632e4a926be0220ccbd710c7d7b9","025a71cc5009ae07e3e991f78212e99dd5be7adf941766d011197f331ce8c1bed0"],["032d3b42ed4913a134145f004cf105b66ae97a9914c35fb73d37170d37271acfcd","0322adeb83151937ddcd32d5bf2d3ed07c245811d0f7152716f82120f21fb25426"],["0312759ff0441c59cb477b5ec1b22e76a794cd821c13b8900d72e34e9848f088c2","02d868626604046887d128388e86c595483085f86a395d68920e244013b544ef3b"],["038c4d5f49ab08be619d4fed7161c339ea37317f92d36d4b3487f7934794b79df4","03f4afb40ae7f4a886f9b469a81168ad549ad341390ff91ebf043c4e4bfa05ecc1"],["02378b36e9f84ba387f0605a738288c159a5c277bbea2ea70191ade359bc597dbb","029fd6f0ee075a08308c0ccda7ace4ad9107573d2def988c2e207ac1d69df13355"],["02cfecde7f415b0931fc1ec06055ff127e9c3bec82af5e3affb15191bf995ffc1a","02abb7481504173a7aa1b9860915ef62d09a323425f680d71746be6516f0bb4acf"]],"xpubs":["xpub661MyMwAqRbcF4mZnFnBRYGBaiD9aQRp9w2jaPUrDg3Eery5gywV7eFMzQKmNyY1W4m4fUwsinMw1tFhMNEZ9KjNtkUSBHPXdcXBwCg5ctV","xpub661MyMwAqRbcGHU5H41miJ2wXBLYYk4psK7pB5pWyxK6m5EARwLrKtmpnMzP52qGsKZEtjJCyohVEaZTFXbohjVdfpDFifgMBT82EvkFpsW"]}},"accounts_expanded":{},"addr_history":{"329Ju5tiAr4vHZExAT4KydYEkfKiHraY2N":[],"32HJ13iTVh3sCWyXzipcGb1e78ZxcHrQ7v":[],"32cAdiAapUzNVRYXmDud5J5vEDcGsPHjD8":[],"33fKLmoCo8oFfeV987P6KrNTghSHjJM251":[],"34cE6ZcgXvHEyKbEP2Jpz5C3aEWhvPoPG2":[],"36xsnTKKBojYRHEApVR6bCFbDLp9oqNAxU":[],"372PG6D3chr8tWF3J811dKSpPS84MPU6SE":[],"378nVF8daT4r3jfX1ebKRheUVZX5zaa9wd":[],"392ZtXKp2THrk5VtbandXxFLB8yr2g14aA":[],"39cCrU3Zz3SsHiQUDiyPS1Qd5ZL3Rh1GhQ":[],"3A2cRoBdem5tdRjq514Pp7ZvaxydgZiaNG":[],"3Ceoi3MKdh2xiziHDAzmriwjDx4dvxxLzm":[],"3FcXdG8mh1YeQCYVib8Aw7zwnKpComimLH":[],"3J4b31yAbQkKhejSW7Qz54qNJDEy3t9uSe":[],"3JpJrSxE1GP1X5h82zvLA2TbMZ8nUsGW6z":[],"3K1dzpbcop1MotuqyFQyEuXbvQehaKnGVM":[],"3L8Us8SN22Hj6GnZPRCLaowA1ZtbptXxxL":[],"3LANyoJyShQ8w55tvopoGiZ2BTVjLfChiP":[],"3LoJGQdXTzVaDYudUguP4jNJYy4gNDaRpN":[],"3MD8jVH7Crp5ucFomDnWqB6kQrEQ9VF5xv":[],"3ME8DemkFJSn2tHS23yuk2WfaMP86rd3s7":[],"3MFNr17oSZpFtH16hGPgXz2em2hJkd3SZn":[],"3QHRTYnW2HWCWoeisVcy3xsAFC5xb6UYAK":[],"3QKwygVezHFBthudRUh8V7wwtWjZk3whpB":[],"3QNPY3dznFwRv6VMcKgmn8FGJdsuSRRjco":[],"3QNwwD8dp6kvS8Fys4ZxVJYZAwCXdXQBKo":[]},"master_private_keys":{"x1/":"xprv9s21ZrQH143K3oPcB2UmMA6Cy9W49HLyW6CDNhQuRcn7tGu1tQ2bn6TLw8HFWbu5oP38Z2fFCo5Q4n3fog4DTqywYqfSDWhYbDgVD1TGZoP"},"master_public_keys":{"x1/":"xpub661MyMwAqRbcGHU5H41miJ2wXBLYYk4psK7pB5pWyxK6m5EARwLrKtmpnMzP52qGsKZEtjJCyohVEaZTFXbohjVdfpDFifgMBT82EvkFpsW","x2/":"xpub661MyMwAqRbcF4mZnFnBRYGBaiD9aQRp9w2jaPUrDg3Eery5gywV7eFMzQKmNyY1W4m4fUwsinMw1tFhMNEZ9KjNtkUSBHPXdcXBwCg5ctV"},"pruned_txo":{},"seed":"turkey weapon legend tower style multiply tomorrow wet like frame leave cash achieve","seed_version":11,"stored_height":490035,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"2of2","winpos-qt":[610,418,840,400]}' + wallet_str = '{"accounts":{"0":{"change":[["03d0bcdc86a64cc2024c84853e88985f6f30d3dc3f219b432680c338a3996a89ed","024f326d48aa0a62310590b10522b69d250a2439544aa4dc496f7ba6351e6ebbfe"],["03c0416928528a9aaaee558590447ee63fd33fa497deebefcf363b1af90d867762","03db7de16cd6f3dcd0329a088382652bc3e6b21ee1a732dd9655e192c887ed88a7"],["0291790656844c9d9c24daa344c0b426089eadd3952935c58ce6efe00ef1369828","02c2a5493893643102f77f91cba709f11aaab3e247863311d6fc3d3fc82624c3cc"],["023dc976bd1410a7e9f34c230051db58a3f487763f00df1f529b10f55ee85b931c","036c318a7530eedf3584fd8b24c4024656508e35057a0e7654f21e89e121d0bd30"],["02c8820711b39272e9730a1c5c5c78fe39a642b8097f8724b2592cc987017680ce","0380e3ebe0ea075e33acb3f796ad6548fde86d37c62fe8e4f6ab5d2073c1bb1d43"],["0369a32ddd213677a0509c85af514537d5ee04c68114da3bc720faeb3adb45e6f8","0370e85ac01af5e3fd5a5c3969c8bca3e4fc24efb9f82d34d5790e718a507cecb6"]],"m":2,"receiving":[["0207739a9ff4a643e1d4adb03736ec43d13ec897bdff76b40a25d3a16e19e464aa","02372ea4a291aeb1fadb26f36976348fc169fc70514797e53b789a87c9b27cc568"],["0248ae7671882ec87dd6bacf7eb2ff078558456cf5753952cddb5dde08f471f3d6","035bac54828b383545d7b70824a8be2f2d9584f656bfdc680298a38e9383ed9e51"],["02cb99ba41dfbd510cd25491c12bd0875fe8155b5a6694ab781b42bd949252ff26","03b520feba42149947f8b2bbc7e8c03f9376521f20ac7b7f122dd44ab27309d7c6"],["0395902d5ebb4905edd7c4aedecf17be0675a2ffeb27d85af25451659c05cc5198","02b4a01d4bd25cadcbf49900005e8d5060ed9cdc35eb33f2cd65cc45cc7ebc00c5"],["02f9d06c136f05acc94e4572399f17238bb56fa15271e3cb816ae7bb9be24b00b6","035516437612574b2b563929c49308911651205e7cebb621940742e570518f1c50"],["0376a7de3abaee6631bd4441658987c27e0c7eee2190a86d44841ae718a014ee43","03cb702364ffd59cb92b2e2128c18d8a5a255be2b95eb950641c5f17a5a900eecb"],["03240c5e868ecb02c4879ae5f5bad809439fdbd2825769d75be188e34f6e533a67","026b0d05784e4b4c8193443ce60bea162eee4d99f9dfa94a53ae3bc046a8574eeb"],["02d087cccb7dc457074aa9decc04de5a080757493c6aa12fa5d7d3d389cfdb5b8e","0293ab7d0d8bbb2d433e7521a1100a08d75a32a02be941f731d5809b22d86edb33"],["03d1b83ab13c5b35701129bed42c1f1fbe86dd503181ad66af3f4fb729f46a277e","0382ec5e920bc5c60afa6775952760668af42b67d36d369cd0e9acc17e6d0a930d"],["03f1737db45f3a42aebd813776f179d5724fce9985e715feb54d836020b8517bfe","0287a9dfb8ee2adab81ef98d52acd27c25f558d2a888539f7d583ef8c00c34d6dc"],["038eb8804e433023324c1d439cd5fbbd641ca85eadcfc5a8b038cb833a755dac21","0361a7c80f0d9483c416bc63d62506c3c8d34f6233b6d100bb43b6fe8ec39388b9"],["0336437ada4cd35bec65469afce298fe49e846085949d93ef59bf77e1a1d804e4a","0321898ed89df11fcfb1be44bb326e4bb3272464f000a9e51fb21d25548619d377"],["0260f0e59d6a80c49314d5b5b857d1df64d474aba48a37c95322292786397f3dc6","03acd6c9aeac54c9510304c2c97b7e206bbf5320c1e268a2757d400356a30c627b"],["0373dc423d6ee57fac3b9de5e2b87cf36c21f2469f17f32f5496e9e7454598ba8e","031ddc1f40c8b8bf68117e790e2d18675b57166e9521dff1da44ba368be76555b3"],["031878b39bc6e35b33ceac396b429babd02d15632e4a926be0220ccbd710c7d7b9","025a71cc5009ae07e3e991f78212e99dd5be7adf941766d011197f331ce8c1bed0"],["032d3b42ed4913a134145f004cf105b66ae97a9914c35fb73d37170d37271acfcd","0322adeb83151937ddcd32d5bf2d3ed07c245811d0f7152716f82120f21fb25426"],["0312759ff0441c59cb477b5ec1b22e76a794cd821c13b8900d72e34e9848f088c2","02d868626604046887d128388e86c595483085f86a395d68920e244013b544ef3b"],["038c4d5f49ab08be619d4fed7161c339ea37317f92d36d4b3487f7934794b79df4","03f4afb40ae7f4a886f9b469a81168ad549ad341390ff91ebf043c4e4bfa05ecc1"],["02378b36e9f84ba387f0605a738288c159a5c277bbea2ea70191ade359bc597dbb","029fd6f0ee075a08308c0ccda7ace4ad9107573d2def988c2e207ac1d69df13355"],["02cfecde7f415b0931fc1ec06055ff127e9c3bec82af5e3affb15191bf995ffc1a","02abb7481504173a7aa1b9860915ef62d09a323425f680d71746be6516f0bb4acf"]],"xpubs":["xpub661MyMwAqRbcF4mZnFnBRYGBaiD9aQRp9w2jaPUrDg3Eery5gywV7eFMzQKmNyY1W4m4fUwsinMw1tFhMNEZ9KjNtkUSBHPXdcXBwCg5ctV","xpub661MyMwAqRbcGHU5H41miJ2wXBLYYk4psK7pB5pWyxK6m5EARwLrKtmpnMzP52qGsKZEtjJCyohVEaZTFXbohjVdfpDFifgMBT82EvkFpsW"]}},"accounts_expanded":{},"addr_history":{"t3K1uuRJr9ArWtCHr6ssT7SeA1KWo45N42T":[],"t3K9u1P8bU1qToA2Rw9djQQ7ZMnm3R59yjg":[],"t3KUme3ainomy64bRheikD7BqUsoMddveuo":[],"t3LXvM7DLmTarGHY34YCDTfUNwMdNUYq2Sp":[],"t3MUq6u2pWF4qZxe8KT7x7tHxpthnmNe8Bt":[],"t3PqUnnjTA8X91vH4kvEDj1MWU11EbKS8Vh":[],"t3PtzGRdBb2djV9HwEYp8m8Yje6K99wGyju":[],"t3Q1PVaYmYmrSeNiQx5QSZWkPkDiAmYyrx9":[],"t3RuAtrjwzn5TLiYnY1bkfmMFRoAvpJPkuV":[],"t3SUoroThxNETtMTNA9nWZpWYLDX8JUEyVB":[],"t3SuDS8bmd5sVE4nj1RsWwvfqqdAiR2ucUi":[],"t3VXQiNmTc1pZKdmB9botzY3eUcFii5w5Wu":[],"t3YV8dbYufLLEzqbPf1wJ4w6s2z1HcQDYvv":[],"t3awC3MPJZjXvJHnLSYE7CswHYsS3mFL1DC":[],"t3bgurnNMybAc7ik1yRjTHqZWcDKsLuxxG3":[],"t3btF1A1kn8nxQXxjugE6NidXB4qnNvCRVN":[],"t3d15sTrVzM5KguqTKr1Tid35GE5gYHpAhW":[],"t3d2yz8j7R2BjXi8nsEdvQXewS7gp9UgeS9":[],"t3dfuGk3fSKHApBxXR7iWCYUDodFm7Fby1q":[],"t3e5jjphFBBbgWFJhhebdxzCffWRUsYH1S4":[],"t3e6jDzBtDdENdXLKxUo2sqcaq1aCqekuaZ":[],"t3e7yrLXwQtbrUv3zdhCofo8a1gtPbWU6XG":[],"t3hA2TtCdzcHo7ShcovS6Bmy5VrH3Ux4fKR":[],"t3hCYz1unxc2nVLxXMuWFcw3s9AveZmZU3d":[],"t3hEzYP48kaj2WjYFYkVtuwMBZJ4z7uVBtQ":[],"t3hFYwYYmnRYX2mJsoVP5d7eURbPcUALaWs":[]},"master_private_keys":{"x1/":"xprv9s21ZrQH143K3oPcB2UmMA6Cy9W49HLyW6CDNhQuRcn7tGu1tQ2bn6TLw8HFWbu5oP38Z2fFCo5Q4n3fog4DTqywYqfSDWhYbDgVD1TGZoP"},"master_public_keys":{"x1/":"xpub661MyMwAqRbcGHU5H41miJ2wXBLYYk4psK7pB5pWyxK6m5EARwLrKtmpnMzP52qGsKZEtjJCyohVEaZTFXbohjVdfpDFifgMBT82EvkFpsW","x2/":"xpub661MyMwAqRbcF4mZnFnBRYGBaiD9aQRp9w2jaPUrDg3Eery5gywV7eFMzQKmNyY1W4m4fUwsinMw1tFhMNEZ9KjNtkUSBHPXdcXBwCg5ctV"},"pruned_txo":{},"seed":"turkey weapon legend tower style multiply tomorrow wet like frame leave cash achieve","seed_version":11,"stored_height":490035,"transactions":{},"txi":{},"txo":{},"use_encryption":false,"wallet_type":"2of2","winpos-qt":[610,418,840,400]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_7_18_seeded(self): - wallet_str = '{"addr_history":{"12nzqpb4vxiFmcvypswSWK1f4cvGwhYAE8":[],"13sapXcP5Wq25PiXh5Zr9mLhyjdfrppWyi":[],"14EzC5y5eFCXg4T7cH4hXoivzysEpGXBTM":[],"15PUQBi2eEzprCZrS8dkfXuoNv8TuqwoBm":[],"16NvXzjxHbiNAULoRRTBjSmecMgF87FAtb":[],"16oyPjLM4R96aZCnSHqBBkDMgbE2ehDWFe":[],"1BfhL8ZPcaZkXTZKASQYcFJsPfXNwCwVMV":[],"1Bn3vun14mDWBDkx4PvK2SyWK1nqB9MSmM":[],"1BrCEnhf763JhVNcZsjGcNmmisBfRkrdcn":[],"1BvXCwXAdaSTES4ENALv3Tw6TJcZbMzu5o":[],"1C2vzgDyPqtvzFRYUgavoLvk3KGujkUUjg":[],"1CN22zUHuX5SxGTmGvPTa2X6qiCJZjDUAW":[],"1CUT9Su42c4MFxrfbrouoniuhVuvRjsKYS":[],"1DLaXDPng4wWXW7AdDG3cLkuKXgEUpjFHq":[],"1DTLcXN6xPUVXP1ZQmt2heXe2KHDSdvRNv":[],"1F1zYJag8yXVnDgGGy7waQT3Sdyp7wLZm3":[],"1Fim67c46NHTcSUu329uF8brTmkoiz6Ej8":[],"1Go6JcgkfZuA7fyQFKuLddee9hzpo31uvL":[],"1J6mhetXo9Eokq7NGjwbKnHryxUCpgbCDn":[],"1K9sFmS7qM2P5JpVGQhHMqQgAnNiujS5jZ":[],"1KBdFn9tGPYEqXnHyJAHxBfCQFF9v3mq95":[],"1LRWRLWHE2pdMviVeTeJBa8nFbUTWSCvrg":[],"1LpXAktoSKbRx7QFkyb2KkSNJXSGLtTg9T":[],"1LtxCQLTqD1q5Q5BReP932t5D7pKx5wiap":[],"1MX5AS3pA5jBhmg4DDuDQEuNhPGS4cGU4F":[],"1Pz9bYFMeqZkXahx9yPjXtJwL69zB3xCp2":[]},"keystore":{"seed":"giraffe tuition frog desk airport rural since dizzy regular victory mind coconut","type":"bip32","xprv":"xprv9s21ZrQH143K28Jvnpm7hU3xPt18neaDpcpoMKTyi9ewNRg6puJ2RAE5gZNPQ73bbmU9WsagxLQ3a6i2t1M9W289HY9Q5sEzFsLaYq3ZQf3","xpub":"xpub661MyMwAqRbcEcPPtrJ84bzgwuqdC7J5BqkQ9hsbGVBvFE1FNScGxxYZXpC9ncowEe7EZVbAerSypw3wCjrmLmsHeG3RzySw5iEJhAfZaZT"},"pruned_txo":{},"pubkeys":{"change":["033e860b0823ed2bf143594b07031d9d95d35f6e4ad6093ddc3071b8d2760f133f","03f51e8798a1a46266dee899bada3e1517a7a57a8402deeef30300a8918c81889a","0308168b05810f62e3d08c61e3c545ccbdce9af603adbdf23dcc366c47f1c5634c","03d7eddff48be72310347efa93f6022ac261cc33ee0704cdad7b6e376e9f90f574","0287e34a1d3fd51efdc83f946f2060f13065e39e587c347b65a579b95ef2307d45","02df34e258a320a11590eca5f0cb0246110399de28186011e8398ce99dd806854a"],"receiving":["031082ff400cbe517cc2ae37492a6811d129b8fb0a8c6bd083313f234e221527ae","03fac4d7402c0d8b290423a05e09a323b51afebd4b5917964ba115f48ab280ef07","03c0a8c4ab604634256d3cfa350c4b6ca294a4374193055195a46626a6adea920f","03b0bc3112231a9bea6f5382f4324f23b4e2deb5f01a90b0fe006b816367e43958","03a59c08c8e2d66523c888416e89fa1aaec679f7043aa5a9145925c7a80568e752","0346fefc07ab2f38b16c8d979a8ffe05bc9f31dd33291b4130797fa7d78f6e4a35","025eb34724546b3c6db2ee8b59fbc4731bafadac5df51bd9bbb20b456d550ef56e","02b79c26e2eac48401d8a278c63eec84dc5bef7a71fa7ce01a6e333902495272e2","03a3a212462a2b12dc33a89a3e85684f3a02a647db3d7eaae18c029a6277c4f8ac","02d13fc5b57c4d057accf42cc918912221c528907a1474b2c6e1b9ca24c9655c1a","023c87c3ca86f25c282d9e6b8583b0856a4888f46666b413622d72baad90a25221","030710e320e9911ebfc89a6b377a5c2e5ae0ab16b9a3df54baa9dbd3eb710bf03c","03406b5199d34be50725db2fcd440e487d13d1f7611e604db81bb06cdd9077ffa5","0378139461735db84ff4d838eb408b9c124e556cfb6bac571ed6b2d0ec671abd0c","030538379532c476f664d8795c0d8e5d29aea924d964c685ea5c2343087f055a82","02d1b93fa37b824b4842c46ef36e5c50aadbac024a6f066b482be382bec6b41e5a","02d64e92d12666cde831eb21e00079ecfc3c4f64728415cc38f899aca32f1a5558","0347480bf4d321f5dce2fcd496598fbdce19825de6ed5b06f602d66de7155ac1c0","03242e3dfd8c4b6947b0fbb0b314620c0c3758600bb842f0848f991e9a2520a81c","021acadf6300cb7f2cca11c6e1c7e59e3cf923a786f6371c3b85dd6f8b65c68470"]},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[709,314,840,405]}' + wallet_str = '{"addr_history":{"t1KfbrA1CuHVrNFysmJkZe87aKH7MdyhDYo":[],"t1LkBps2X3qccg2mRdWNyHaSdEPpkcemiJK":[],"t1M7bCRPDcZz8GhW1YhspfcprFe4KYEGwLb":[],"t1NG5QX8AcZnRSqckNZSsoM1idaKYhfLhXT":[],"t1PFXYLA6FvVxm7PhMrGJsFsZs1sKtgtULZ":[],"t1PgaQ4kV2jvhBCFgNieJKZKGwFR7TfaTxD":[],"t1UYJLTyXauMM86cD6sDfk4QneKiTfj9Agx":[],"t1UeewFC93616mroqzpjSAG5RZfyuxN8ezv":[],"t1UioF87o5QpuJ8RWWJYPkBsgyXNkE1Sj7i":[],"t1Uo8DGwJbuE3q578JbA3BH31hxoeMHvfEd":[],"t1UuY11e7NAgXatUSR7Q3wA2fHyTzd7iHXC":[],"t1VEd3KtRsqs3YuWfDMCahqd26NPPM6UQ1W":[],"t1VM49nKBzvqwrbuZYHd2wbppxA71GMocpM":[],"t1WDBXYovePj789A4Ze5Ak9rpaBsKKpPtpv":[],"t1WKwcrnEviG6824TMCh9qTdZGyUJJRKCdD":[],"t1XtbYdzp7JK6NrjADPw4iDYxhJAtpmUPZ5":[],"t1YbN6T2C4h54D5XnySy2NwhmiRwtY85EaN":[],"t1ZfhJx6tdtgkiK2JBkiTmSkZQNBudqjeFh":[],"t1ayNhzJfmU2QMUAGDAkiTbPnEcfHckSHEv":[],"t1c2UG6rFofoyfwsPCqWQVeWbRSZoimL4ab":[],"t1c4EG7a2EiKqSAqBuiyR5zm7euSEdoXXLX":[],"t1dJ7RfvRCMcDxZmPatTRKPEhWFfYLZbr8u":[],"t1dh8B6JwQeP2YkT9hQQ9TZYHZBdM9cxg86":[],"t1dmZCjkboXoRg385N5CGAqyzTn1QmnE7H5":[],"t1ePgAmTx8QWnJQix9eiLY41Hx3TWuQo2Zg":[],"t1grkbsfVdAMM8Dkr6QCrfhQrakM4xQQFhb":[]},"keystore":{"seed":"giraffe tuition frog desk airport rural since dizzy regular victory mind coconut","type":"bip32","xprv":"xprv9s21ZrQH143K28Jvnpm7hU3xPt18neaDpcpoMKTyi9ewNRg6puJ2RAE5gZNPQ73bbmU9WsagxLQ3a6i2t1M9W289HY9Q5sEzFsLaYq3ZQf3","xpub":"xpub661MyMwAqRbcEcPPtrJ84bzgwuqdC7J5BqkQ9hsbGVBvFE1FNScGxxYZXpC9ncowEe7EZVbAerSypw3wCjrmLmsHeG3RzySw5iEJhAfZaZT"},"pruned_txo":{},"pubkeys":{"change":["033e860b0823ed2bf143594b07031d9d95d35f6e4ad6093ddc3071b8d2760f133f","03f51e8798a1a46266dee899bada3e1517a7a57a8402deeef30300a8918c81889a","0308168b05810f62e3d08c61e3c545ccbdce9af603adbdf23dcc366c47f1c5634c","03d7eddff48be72310347efa93f6022ac261cc33ee0704cdad7b6e376e9f90f574","0287e34a1d3fd51efdc83f946f2060f13065e39e587c347b65a579b95ef2307d45","02df34e258a320a11590eca5f0cb0246110399de28186011e8398ce99dd806854a"],"receiving":["031082ff400cbe517cc2ae37492a6811d129b8fb0a8c6bd083313f234e221527ae","03fac4d7402c0d8b290423a05e09a323b51afebd4b5917964ba115f48ab280ef07","03c0a8c4ab604634256d3cfa350c4b6ca294a4374193055195a46626a6adea920f","03b0bc3112231a9bea6f5382f4324f23b4e2deb5f01a90b0fe006b816367e43958","03a59c08c8e2d66523c888416e89fa1aaec679f7043aa5a9145925c7a80568e752","0346fefc07ab2f38b16c8d979a8ffe05bc9f31dd33291b4130797fa7d78f6e4a35","025eb34724546b3c6db2ee8b59fbc4731bafadac5df51bd9bbb20b456d550ef56e","02b79c26e2eac48401d8a278c63eec84dc5bef7a71fa7ce01a6e333902495272e2","03a3a212462a2b12dc33a89a3e85684f3a02a647db3d7eaae18c029a6277c4f8ac","02d13fc5b57c4d057accf42cc918912221c528907a1474b2c6e1b9ca24c9655c1a","023c87c3ca86f25c282d9e6b8583b0856a4888f46666b413622d72baad90a25221","030710e320e9911ebfc89a6b377a5c2e5ae0ab16b9a3df54baa9dbd3eb710bf03c","03406b5199d34be50725db2fcd440e487d13d1f7611e604db81bb06cdd9077ffa5","0378139461735db84ff4d838eb408b9c124e556cfb6bac571ed6b2d0ec671abd0c","030538379532c476f664d8795c0d8e5d29aea924d964c685ea5c2343087f055a82","02d1b93fa37b824b4842c46ef36e5c50aadbac024a6f066b482be382bec6b41e5a","02d64e92d12666cde831eb21e00079ecfc3c4f64728415cc38f899aca32f1a5558","0347480bf4d321f5dce2fcd496598fbdce19825de6ed5b06f602d66de7155ac1c0","03242e3dfd8c4b6947b0fbb0b314620c0c3758600bb842f0848f991e9a2520a81c","021acadf6300cb7f2cca11c6e1c7e59e3cf923a786f6371c3b85dd6f8b65c68470"]},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[709,314,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_7_18_importedkeys(self): - wallet_str = '{"addr_history":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":[],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":[],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":[]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"pubkeys":{"change":[],"receiving":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2"]},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[420,312,840,405]}' + wallet_str = '{"addr_history":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":[],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":[],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":[]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"pubkeys":{"change":[],"receiving":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2"]},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[420,312,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_7_18_watchaddresses(self): - wallet_str = '{"addr_history":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[]},"addresses":["1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs","1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa","1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf"],"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"verified_tx3":{},"wallet_type":"imported","winpos-qt":[553,402,840,405]}' + wallet_str = '{"addr_history":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[]},"addresses":["t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq","t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu","t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j"],"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"verified_tx3":{},"wallet_type":"imported","winpos-qt":[553,402,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_7_18_trezor_singleacc(self): - wallet_str = '''{"addr_history":{"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC":[],"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz":[],"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM":[],"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ":[],"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6":[],"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S":[],"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH":[],"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw":[],"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb":[],"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX":[],"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ":[],"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp":[],"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk":[],"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD":[],"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp":[],"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz":[],"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs":[],"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid":[],"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu":[],"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo":[],"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj":[],"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv":[],"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC":[],"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe":[],"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL":[],"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG":[]},"keystore":{"derivation":"m/44'/0'/0'","hw_type":"trezor","label":"trezor1","type":"hardware","xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"pruned_txo":{},"pubkeys":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"]},"seed_version":13,"stored_height":490013,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[631,410,840,405]}''' + wallet_str = '''{"addr_history":{"t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy":[],"t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu":[],"t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5":[],"t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM":[],"t1Njrm3uzEuQg1MNapyYnpXEMhJRkCAxc1d":[],"t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce":[],"t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ":[],"t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49":[],"t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw":[],"t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk":[],"t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq":[],"t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P":[],"t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C":[],"t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i":[],"t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth":[],"t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby":[],"t1cgVBNfdDwLVsoYXMnymANTvc5RuFGLm1w":[],"t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF":[],"t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE":[],"t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi":[],"t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn":[],"t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8":[],"t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM":[],"t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R":[],"t1fSU5JTkH2hM6GNLUeWhrAPcdikYsBwgdX":[],"t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya":[]},"keystore":{"derivation":"m/44'/0'/0'","hw_type":"trezor","label":"trezor1","type":"hardware","xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"pruned_txo":{},"pubkeys":{"change":["03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902","03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740","028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee","021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8","031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4","033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d"],"receiving":["03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0","024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97","03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b","028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136","02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5","02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4","023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53","02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4","029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92","02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066","0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447","0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea","02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027","0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50","03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459","0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c","028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804","03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736","029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f","02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105"]},"seed_version":13,"stored_height":490013,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[631,410,840,405]}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_7_18_multisig(self): - wallet_str = '{"addr_history":{"32WKXQ6BWtGJDVTpdcUMhtRZWzgk5eKnhD":[],"33rvo2pxaccCV7jLwvth36sdLkdEqhM8B8":[],"347kG9dzt2M1ZPTa2zzcmVrAE75LuZs9A2":[],"34BBeAVEe5AM6xkRebddFG8JH6Vx1M5hHH":[],"34MAGbxxCHPX8ASfKsyNkzpqPEUTZ5i1Kx":[],"36uNpoPSgUhN5Cc1wRQyL77aD1RL3a9X6f":[],"384xygkfYsSuXN478zhN4jmNcky1bPo7Cq":[],"39GBGaGpp1ePBsjjaw8NmbZNZkMzhfmZ3W":[],"3BRhw13g9ShGcuHbHExxtFfvhjrxiSiA7J":[],"3BboKZc2VgjKVxoC5gndLGpwEkPJuQrZah":[],"3C3gKJ2UQNNHY2SG4h43zRS1faSLhnqQEr":[],"3CEY1V5WvCTxjHEPG5BY4eXpcYhakTvULJ":[],"3DJyQ94H9g18PR6hfzZNxwwdU6773JaYHd":[],"3Djb7sWog5ANggPWHm4xT5JiTrTSCmVQ8N":[],"3EfgjpUeJBhp3DcgP9wz3EhHNdkCbiJe2L":[],"3FWgjvaL8xN6ne19WCEeD5xxryyKAQ5tn1":[],"3H4ZtDFovXxwWXCpRo8mrCczjTrtbT6eYL":[],"3HvnjPzpaE3VGWwGTALZBguT8p9fyAcfHS":[],"3JGuY9EpzuZkDLR7vVGhqK7zmX9jhYEfmD":[],"3JvrP4gpCUeQzqgPyDt2XePXn3kpqFTo9i":[],"3K3TVvsfo52gdwz7gk84hfP77gRmpc3hkf":[],"3K5uh5viV4Dac267Q3eNurQQBnpEbYck5G":[],"3KaoWE1m3QrtvxTQLFfvNs8gwQH8kQDpFM":[],"3Koo71MC4wBfiDKTsck7qCrRjtGx2SwZqT":[],"3L8XBt8KxwqNX1vJprp6C9YfNW4hkYrC6d":[],"3QmZjxPwcsHZgVUR2gQ6wdbGJBbFro8KLJ":[]},"pruned_txo":{},"pubkeys":{"change":[["031bfbbfb36b5e526bf4d94bfc59f170177b2c821f7d4d4c0e1ee945467fe031a0","03c4664d68e3948e2017c5c55f7c1aec72c1c15686b07875b0f20d5f856ebeb703"],["03c515314e4b695a809d3ba08c20bef00397a0e2df729eaf17b8e082825395e06b","032391d8ab8cad902e503492f1051129cee42dc389231d3cdba60541d70e163244"],["035934f55c09ecec3e8f2aa72407ee7ba3c2f077be08b92a27bc4e81b5e27643fe","0332b121ed13753a1f573feaf4d0a94bf5dd1839b94018844a30490dd501f5f5fb"],["02b1367f7f07cbe1ef2c75ac83845c173770e42518da20efde3239bf988dbff5ac","03f3a8b9033b3545fbe47cab10a6f42c51393ed6e525371e864109f0865a0af43c"],["02e7c25f25ecc17969a664d5225c37ec76184a8843f7a94655f5ed34b97c52445d","030ae4304923e6d8d6cd67324fa4c8bc44827918da24a05f9240df7c91c8e8db8f"],["02deb653a1d54372dbc8656fe0a461d91bcaec18add290ccaa742bdaefdb9ec69b","023c1384f90273e3fc8bc551e71ace8f34831d4a364e56a6e778cd802b7f7965a6"]],"receiving":[["02d978f23dc1493db4daf066201f25092d91d60c4b749ca438186764e6d80e6aa1","02912a8c05d16800589579f08263734957797d8e4bc32ad7411472d3625fd51f10"],["024a4b4f2553d7f4cc2229922387aad70e5944a5266b2feb15f453cedbb5859b13","03f8c6751ee93a0f4afb7b2263982b849b3d4d13c2e30b3f8318908ad148274b4b"],["03cd88a88aabc4b833b4631f4ffb4b9dc4a0845bb7bc3309fab0764d6aa08c4f25","03568901b1f3fb8db05dd5c2092afc90671c3eb8a34b03f08bcfb6b20adf98f1cd"],["030530ffe2e4a41312a41f708febab4408ca8e431ce382c1eedb837901839b550d","024d53412197fc609a6ca6997c6634771862f2808c155723fac03ea89a5379fdcc"],["02de503d2081b523087ca195dbae55bafb27031a918a1cfedbd2c4c0da7d519902","03f4a27a98e41bddb7543bf81a9c53313bf9cfb2c2ebdb6bf96551221d8aecb01a"],["03504bc595ac0d947299759871bfdcf46bcdd8a0590c44a78b8b69f1b152019418","0291f188301773dbc7c1d12e88e3aa86e6d4a88185a896f02852141e10e7e986ab"],["0389c3ab262b7994d2202e163632a264f49dd5f78517e01c9210b6d0a29f524cd4","034bdfa9cc0c6896cb9488329d14903cfe60a2879771c5568adfc452f8dba1b2cb"],["02c55a517c162aae2cb5b36eef78b51aa15040e7293033a5b55ba299e375da297d","027273faf29e922d95987a09c2554229becb857a68112bd139409eb111e7cdb45e"],["02401e62d645dc64d43f77ba1f360b529a4c644ed3fc15b35932edafbaf741e844","02c44cbffc13cb53134354acd18c54c59fa78ec61307e147fa0f6f536fb030a675"],["02194a538f37b388b2b138f73a37d7fbb9a3e62f6b5a00bad2420650adc4fb44d9","03e5cc15d47fcdcf815baa0e15227bc5e6bd8af6cae6add71f724e95bc29714ce5"],["037ebf7b2029c8ea0c1861f98e0952c544a38b9e7caebbf514ff58683063cd0e78","022850577856c810dead8d3d44f28a3b71aaf21cdc682db1beb8056408b1d57d52"],["02aea7537611754fdafd98f341c5a6827f8301eaf98f5710c02f17a07a8938a30e","032fa37659a8365fdae3b293a855c5a692faca687b0875e9720219f9adf4bdb6c2"],["0224b0b8d200238495c58e1bc83afd2b57f9dbb79f9a1fdb40747bebb51542c8d3","03b88cd2502e62b69185b989abb786a57de27431ece4eabb26c934848d8426cbd6"],["032802b0be2a00a1e28e1e29cfd2ad79d36ef936a0ef1c834b0bbe55c1b2673bff","032669b2d80f9110e49d49480acf696b74ecca28c21e7d9c1dd2743104c54a0b13"],["03fcfa90eac92950dd66058bbef0feb153e05a114af94b6843d15200ef7cf9ea4a","023246268fbe8b9a023d9a3fa413f666853bbf92c4c0af47731fdded51751e0c3a"],["020cf5fffe70b174e242f6193930d352c54109578024677c1a13ffce5e1f9e6a29","03cb996663b9c895c3e04689f0cf1473974023fa0d59416be2a0b01ccdaa3cc484"],["03467e4fff9b33c73b0140393bde3b35a3f804bce79eccf9c53a1f76c59b7452bd","03251c2a041e953c8007d9ee838569d6be9eacfbf65857e875d87c32a8123036d8"],["02192e19803bfa6f55748aada33f778f0ebb22a1c573e5e49cba14b6a431ef1c37","02224ce74f1ee47ba6eaaf75618ce2d4768a041a553ee5eb60b38895f3f6de11dc"],["032679be8a73fa5f72d438d6963857bd9e49aef6134041ca950c70b017c0c7d44f","025a8463f1c68e85753bd2d37a640ab586d8259f21024f6173aeed15a23ad4287b"],["03ab0355c95480f0157ae48126f893a6d434aa1341ad04c71517b104f3eda08d3d","02ba4aadba99ae8dc60515b15a087e8763496fcf4026f5a637d684d0d0f8a5f76c"]]},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"2of2","winpos-qt":[523,230,840,405],"x1/":{"seed":"pudding sell evoke crystal try order supply chase fine drive nurse double","type":"bip32","xprv":"xprv9s21ZrQH143K2MK5erSSgeaPA1H7gENYS6grakohkaK2M4tzqo6XAjLoRPcBRW9NbGNpaZN3pdoSKLeiQJwmqdSi3GJWZLnK1Txpbn3zinV","xpub":"xpub661MyMwAqRbcEqPYksyT3nX7i37c5h6PoKcTP9DKJur1DsE9PLQmiXfHGe8RmN538Pj8t3qUQcZXCMrkS5z1uWJ6jf9EptAFbC4Z2nKaEQE"},"x2/":{"type":"bip32","xprv":null,"xpub":"xpub661MyMwAqRbcGYXvLgWjW91feK49GajmPdEarB3Ny8JDduUhzTcEThc8Xs1GyqMR4S7xPHvSq4sbDEFzQh3hjJJFEksUzvnjYnap5RX9o4j"}}' + wallet_str = '{"addr_history":{"t3KNvXjWKVD3tp8Wia3HUqhXUmespncsStE":[],"t3LjXoNF6YwPo5knEtMhpAuyYbQpKa5zSGk":[],"t3LzMGV48rM8cA2WTyRojuJx5UmGRjWpSnK":[],"t3M3neVuNcPwwhboKb2SkP5EDXkh2qYmWCz":[],"t3MDmGwP6AcB7ioVZGJnVtovkdtfYHy1X1D":[],"t3Pmyq8oaeoUxfqeusrE6TvDVTfcQpinWU7":[],"t3QwZz2AoXCEW81715RWVCYsHsRA6MbUZ4o":[],"t3S8nGugxnLRynWndXMwVuQfHpQZ5WRx5Rr":[],"t3UJJwLTp7mUsDYLVDfn624mqxQ43UAneQu":[],"t3UUQKu2AU1Wv6br627bkU5vrVQaPkhbRM7":[],"t3UvHKdScNh9t8fVA17sB8EXvvEdRXxpVsF":[],"t3V791pVetXFZKvHHCVzfCTdjsCtfUaCLmT":[],"t3WBaQUUR7zniz49bcRNW6m3YikJBk7jBgT":[],"t3WcC8CvwePwyHKSQEBt5atQdiWeWzQJSfp":[],"t3XYHk9tnGWVQdrfaKam7B3oCdHwHSBxGoF":[],"t3YPHkFzU7H9hPH43Sd3mLu4t7eAPyh8aVM":[],"t3ZwAtYfwtrkY7AFiNDwtz1iuz83yRhNw7f":[],"t3aoPjjQxYYq5s9zAPb9gKW1NPULkiB2xst":[],"t3b9WYUexyEMLoyU1rv5py8Dv2BLpSG3k72":[],"t3boTPQ6xAoS1bUjHueh9fTVT2hwua4s4WE":[],"t3bv4WGHomPpHEb31dAwBqUV2NLcrdSrXCY":[],"t3bxWhRLrTP1BCf91LUTW3fWKST1KQVUnHM":[],"t3cTQWZRu1jeVXbWJGgV3WgEcC4UDY92DHe":[],"t3cgQ7LmL3FyGJrNMp3ZEy1xLzYU2srZKM8":[],"t3d18CDYTwGcy7eyCmHdDKxeadAFnWkDMoS":[],"t3heAkHp5bC5AH8XJy7DE5ShBYqnLcHv6rt":[]},"pruned_txo":{},"pubkeys":{"change":[["031bfbbfb36b5e526bf4d94bfc59f170177b2c821f7d4d4c0e1ee945467fe031a0","03c4664d68e3948e2017c5c55f7c1aec72c1c15686b07875b0f20d5f856ebeb703"],["03c515314e4b695a809d3ba08c20bef00397a0e2df729eaf17b8e082825395e06b","032391d8ab8cad902e503492f1051129cee42dc389231d3cdba60541d70e163244"],["035934f55c09ecec3e8f2aa72407ee7ba3c2f077be08b92a27bc4e81b5e27643fe","0332b121ed13753a1f573feaf4d0a94bf5dd1839b94018844a30490dd501f5f5fb"],["02b1367f7f07cbe1ef2c75ac83845c173770e42518da20efde3239bf988dbff5ac","03f3a8b9033b3545fbe47cab10a6f42c51393ed6e525371e864109f0865a0af43c"],["02e7c25f25ecc17969a664d5225c37ec76184a8843f7a94655f5ed34b97c52445d","030ae4304923e6d8d6cd67324fa4c8bc44827918da24a05f9240df7c91c8e8db8f"],["02deb653a1d54372dbc8656fe0a461d91bcaec18add290ccaa742bdaefdb9ec69b","023c1384f90273e3fc8bc551e71ace8f34831d4a364e56a6e778cd802b7f7965a6"]],"receiving":[["02d978f23dc1493db4daf066201f25092d91d60c4b749ca438186764e6d80e6aa1","02912a8c05d16800589579f08263734957797d8e4bc32ad7411472d3625fd51f10"],["024a4b4f2553d7f4cc2229922387aad70e5944a5266b2feb15f453cedbb5859b13","03f8c6751ee93a0f4afb7b2263982b849b3d4d13c2e30b3f8318908ad148274b4b"],["03cd88a88aabc4b833b4631f4ffb4b9dc4a0845bb7bc3309fab0764d6aa08c4f25","03568901b1f3fb8db05dd5c2092afc90671c3eb8a34b03f08bcfb6b20adf98f1cd"],["030530ffe2e4a41312a41f708febab4408ca8e431ce382c1eedb837901839b550d","024d53412197fc609a6ca6997c6634771862f2808c155723fac03ea89a5379fdcc"],["02de503d2081b523087ca195dbae55bafb27031a918a1cfedbd2c4c0da7d519902","03f4a27a98e41bddb7543bf81a9c53313bf9cfb2c2ebdb6bf96551221d8aecb01a"],["03504bc595ac0d947299759871bfdcf46bcdd8a0590c44a78b8b69f1b152019418","0291f188301773dbc7c1d12e88e3aa86e6d4a88185a896f02852141e10e7e986ab"],["0389c3ab262b7994d2202e163632a264f49dd5f78517e01c9210b6d0a29f524cd4","034bdfa9cc0c6896cb9488329d14903cfe60a2879771c5568adfc452f8dba1b2cb"],["02c55a517c162aae2cb5b36eef78b51aa15040e7293033a5b55ba299e375da297d","027273faf29e922d95987a09c2554229becb857a68112bd139409eb111e7cdb45e"],["02401e62d645dc64d43f77ba1f360b529a4c644ed3fc15b35932edafbaf741e844","02c44cbffc13cb53134354acd18c54c59fa78ec61307e147fa0f6f536fb030a675"],["02194a538f37b388b2b138f73a37d7fbb9a3e62f6b5a00bad2420650adc4fb44d9","03e5cc15d47fcdcf815baa0e15227bc5e6bd8af6cae6add71f724e95bc29714ce5"],["037ebf7b2029c8ea0c1861f98e0952c544a38b9e7caebbf514ff58683063cd0e78","022850577856c810dead8d3d44f28a3b71aaf21cdc682db1beb8056408b1d57d52"],["02aea7537611754fdafd98f341c5a6827f8301eaf98f5710c02f17a07a8938a30e","032fa37659a8365fdae3b293a855c5a692faca687b0875e9720219f9adf4bdb6c2"],["0224b0b8d200238495c58e1bc83afd2b57f9dbb79f9a1fdb40747bebb51542c8d3","03b88cd2502e62b69185b989abb786a57de27431ece4eabb26c934848d8426cbd6"],["032802b0be2a00a1e28e1e29cfd2ad79d36ef936a0ef1c834b0bbe55c1b2673bff","032669b2d80f9110e49d49480acf696b74ecca28c21e7d9c1dd2743104c54a0b13"],["03fcfa90eac92950dd66058bbef0feb153e05a114af94b6843d15200ef7cf9ea4a","023246268fbe8b9a023d9a3fa413f666853bbf92c4c0af47731fdded51751e0c3a"],["020cf5fffe70b174e242f6193930d352c54109578024677c1a13ffce5e1f9e6a29","03cb996663b9c895c3e04689f0cf1473974023fa0d59416be2a0b01ccdaa3cc484"],["03467e4fff9b33c73b0140393bde3b35a3f804bce79eccf9c53a1f76c59b7452bd","03251c2a041e953c8007d9ee838569d6be9eacfbf65857e875d87c32a8123036d8"],["02192e19803bfa6f55748aada33f778f0ebb22a1c573e5e49cba14b6a431ef1c37","02224ce74f1ee47ba6eaaf75618ce2d4768a041a553ee5eb60b38895f3f6de11dc"],["032679be8a73fa5f72d438d6963857bd9e49aef6134041ca950c70b017c0c7d44f","025a8463f1c68e85753bd2d37a640ab586d8259f21024f6173aeed15a23ad4287b"],["03ab0355c95480f0157ae48126f893a6d434aa1341ad04c71517b104f3eda08d3d","02ba4aadba99ae8dc60515b15a087e8763496fcf4026f5a637d684d0d0f8a5f76c"]]},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"2of2","winpos-qt":[523,230,840,405],"x1/":{"seed":"pudding sell evoke crystal try order supply chase fine drive nurse double","type":"bip32","xprv":"xprv9s21ZrQH143K2MK5erSSgeaPA1H7gENYS6grakohkaK2M4tzqo6XAjLoRPcBRW9NbGNpaZN3pdoSKLeiQJwmqdSi3GJWZLnK1Txpbn3zinV","xpub":"xpub661MyMwAqRbcEqPYksyT3nX7i37c5h6PoKcTP9DKJur1DsE9PLQmiXfHGe8RmN538Pj8t3qUQcZXCMrkS5z1uWJ6jf9EptAFbC4Z2nKaEQE"},"x2/":{"type":"bip32","xprv":null,"xpub":"xpub661MyMwAqRbcGYXvLgWjW91feK49GajmPdEarB3Ny8JDduUhzTcEThc8Xs1GyqMR4S7xPHvSq4sbDEFzQh3hjJJFEksUzvnjYnap5RX9o4j"}}' self._upgrade_storage(wallet_str) # seed_version 13 is ambiguous @@ -208,47 +208,47 @@ class TestStorageUpgrade(WalletTestCase): # then opened in 2.8.3, after which a few other new privkeys were imported # it's in some sense in an "inconsistent" state def test_upgrade_from_client_2_8_3_importedkeys_flawed_previous_upgrade_from_2_7_18(self): - wallet_str = '{"addr_history":{"15VBrfYwoXvDWyXHq1myxDv4h36qUmCHcE":[],"179vRrzjT9k7k5oCNCx6eodYCaLKPy9UQn":[],"18o6WCBWdAaM5kjKnyEL4HysoT324rvJu7":[],"1A9F6ZEqmfKeuLeEq5eWFxajgiJfGCc7ar":[],"1BTjGNUmeMSPBTuXTdwD3DLyCugAZaFb7w":[],"1CjW4KM38acCRw3spiFKiZsj7xmmQqqwd8":[],"1EaDNLPwHRraX1N3ecPWJ2mm7NRgdtvpCj":[],"1PYtQBkjXHQX6YtMzEgehN638o784pK3ce":[],"1yT2T4ha3i1GZoK2iP8EpcgSNG34R2ufM":[]},"addresses":{"change":[],"receiving":["1PYtQBkjXHQX6YtMzEgehN638o784pK3ce","1yT2T4ha3i1GZoK2iP8EpcgSNG34R2ufM","1CjW4KM38acCRw3spiFKiZsj7xmmQqqwd8","1A9F6ZEqmfKeuLeEq5eWFxajgiJfGCc7ar","18o6WCBWdAaM5kjKnyEL4HysoT324rvJu7","1EaDNLPwHRraX1N3ecPWJ2mm7NRgdtvpCj","179vRrzjT9k7k5oCNCx6eodYCaLKPy9UQn","1BTjGNUmeMSPBTuXTdwD3DLyCugAZaFb7w","15VBrfYwoXvDWyXHq1myxDv4h36qUmCHcE"]},"keystore":{"keypairs":{"0206b77fd06f212ad7d85f4a054c231ba4e7894b1773dcbb449671ee54618ff5e9":"L52LWS2hB5ev9JYiisFewJH9Q16U7yYcSNt3M8UKLmL5p1q3v2H2","028cda4a0f03cbcbc695d9cac0858081fd5458acfd29564127d329553245afca42":"KzRhkN9Psm9BobcPx3X3VykVA8yhCBrVvE4tTyq6NE283sL6uvYG","02ba4117a24d7e38ae14c429fce0d521aa1fb6bb97558a13f1ef2bc0a476a1741f":"KySXfvidmMBf8iw6m3R9WtdfKcQPWXenwMZtpno5XpfLMNHH8PMn","031bb44462038b97010624a8f8cb15a10fd0d277f12aba3ccf5ce0d36fc6df3112":"KxmcmCvNrZFgy2jyz9W353XbMwCYWHzYTQVzbaDfZM4FLxemgmKh","0339081c4a0ce22c01aa78a5d025e7a109100d1a35ef0f8f06a0d4c5f9ffefc042":"L53Ks569m3H1dRzua3nGzBE3AaEV8dMvBoHDeSJGnWEDeL775mJ5","0339ea71aba2805238e636c2f1b3e5a8308b1dbdbb335787c51f2f6bf3f6218643":"KwHDUpfvnSC58bs3nGy7YpducXkbmo6UUHrydBHy6sT1mRJcVvBo","04e7dc460c87267cf0958d6904d9cd99a4af0d64d61858636aec7a02e5f9a578d27c1329d5ddc45a937130ed4a59e4147cb4907724321baa6a976f9972a17f79ba":"5JECca5E7r1eNgME7NsPdE29XiVCVwXSzEihnhAQXuMdsJ4VL8S","04e9ad0bf70c51c06c2459961175c47cfec59d58ebef4ffcd9836904ef11230afce03ab5eaac5958b538382195b5aea9bf057c0486079869bb72ef9c958f33f1ed":"5Jt9rGLWgxoJUo4eoYEECskLmRA4BkZqHPHg7DdghKBaWarKuxW","04f8cbd67830ab37138c92898a64a4edf836a60aa5b36956547788bd205c635d6a3056fa6a079961384ae336e737d4c45835821c8915dbc5e18a7def88df83946b":"5KRjCNThRDP8aQTJ3Hq9HUSVNRNUB2e69xwLfMUsrXYLXT7U8b9"},"type":"imported"},"pruned_txo":{},"pubkeys":{"change":[],"receiving":["04e9ad0bf70c51c06c2459961175c47cfec59d58ebef4ffcd9836904ef11230afce03ab5eaac5958b538382195b5aea9bf057c0486079869bb72ef9c958f33f1ed","0339081c4a0ce22c01aa78a5d025e7a109100d1a35ef0f8f06a0d4c5f9ffefc042","0339ea71aba2805238e636c2f1b3e5a8308b1dbdbb335787c51f2f6bf3f6218643","02ba4117a24d7e38ae14c429fce0d521aa1fb6bb97558a13f1ef2bc0a476a1741f","028cda4a0f03cbcbc695d9cac0858081fd5458acfd29564127d329553245afca42","04e7dc460c87267cf0958d6904d9cd99a4af0d64d61858636aec7a02e5f9a578d27c1329d5ddc45a937130ed4a59e4147cb4907724321baa6a976f9972a17f79ba","04f8cbd67830ab37138c92898a64a4edf836a60aa5b36956547788bd205c635d6a3056fa6a079961384ae336e737d4c45835821c8915dbc5e18a7def88df83946b"]},"seed_version":13,"stored_height":492756,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' + wallet_str = '{"addr_history":{"t1NMnrzy5mrhp7caBmSb7631ywhHvDmE1k3":[],"t1Q2XSCQsRUXiLir6JdmDncjTTEXQAruVtV":[],"t1RfhWXbebVMwgPnDjQ3TC75o47E6wUudCq":[],"t1T1r6teyjz7FVyh8mWTdPmgewNVk14VY56":[],"t1ULLGhtucgDyn6xRQ4kLB2StTZsFLFH7a1":[],"t1Vc74emB6uPo2a6mm94SrNyeNcxrBxS6u1":[],"t1XSpNfp5FkeB7eQwb3CdRqsgN2cmV6zLEi":[],"t1gRVQXAsVcC7hBwFvfVmqBBxPTJCqQ6e3X":[],"t1Jr42nUqYNVbsCrCy9CFNdibh2T7pJ3aj4":[]},"addresses":{"change":[],"receiving":["t1gRVQXAsVcC7hBwFvfVmqBBxPTJCqQ6e3X","t1Jr42nUqYNVbsCrCy9CFNdibh2T7pJ3aj4","t1Vc74emB6uPo2a6mm94SrNyeNcxrBxS6u1","t1T1r6teyjz7FVyh8mWTdPmgewNVk14VY56","t1RfhWXbebVMwgPnDjQ3TC75o47E6wUudCq","t1XSpNfp5FkeB7eQwb3CdRqsgN2cmV6zLEi","t1Q2XSCQsRUXiLir6JdmDncjTTEXQAruVtV","t1ULLGhtucgDyn6xRQ4kLB2StTZsFLFH7a1","t1NMnrzy5mrhp7caBmSb7631ywhHvDmE1k3"]},"keystore":{"keypairs":{"0206b77fd06f212ad7d85f4a054c231ba4e7894b1773dcbb449671ee54618ff5e9":"L52LWS2hB5ev9JYiisFewJH9Q16U7yYcSNt3M8UKLmL5p1q3v2H2","028cda4a0f03cbcbc695d9cac0858081fd5458acfd29564127d329553245afca42":"KzRhkN9Psm9BobcPx3X3VykVA8yhCBrVvE4tTyq6NE283sL6uvYG","02ba4117a24d7e38ae14c429fce0d521aa1fb6bb97558a13f1ef2bc0a476a1741f":"KySXfvidmMBf8iw6m3R9WtdfKcQPWXenwMZtpno5XpfLMNHH8PMn","031bb44462038b97010624a8f8cb15a10fd0d277f12aba3ccf5ce0d36fc6df3112":"KxmcmCvNrZFgy2jyz9W353XbMwCYWHzYTQVzbaDfZM4FLxemgmKh","0339081c4a0ce22c01aa78a5d025e7a109100d1a35ef0f8f06a0d4c5f9ffefc042":"L53Ks569m3H1dRzua3nGzBE3AaEV8dMvBoHDeSJGnWEDeL775mJ5","0339ea71aba2805238e636c2f1b3e5a8308b1dbdbb335787c51f2f6bf3f6218643":"KwHDUpfvnSC58bs3nGy7YpducXkbmo6UUHrydBHy6sT1mRJcVvBo","04e7dc460c87267cf0958d6904d9cd99a4af0d64d61858636aec7a02e5f9a578d27c1329d5ddc45a937130ed4a59e4147cb4907724321baa6a976f9972a17f79ba":"5JECca5E7r1eNgME7NsPdE29XiVCVwXSzEihnhAQXuMdsJ4VL8S","04e9ad0bf70c51c06c2459961175c47cfec59d58ebef4ffcd9836904ef11230afce03ab5eaac5958b538382195b5aea9bf057c0486079869bb72ef9c958f33f1ed":"5Jt9rGLWgxoJUo4eoYEECskLmRA4BkZqHPHg7DdghKBaWarKuxW","04f8cbd67830ab37138c92898a64a4edf836a60aa5b36956547788bd205c635d6a3056fa6a079961384ae336e737d4c45835821c8915dbc5e18a7def88df83946b":"5KRjCNThRDP8aQTJ3Hq9HUSVNRNUB2e69xwLfMUsrXYLXT7U8b9"},"type":"imported"},"pruned_txo":{},"pubkeys":{"change":[],"receiving":["04e9ad0bf70c51c06c2459961175c47cfec59d58ebef4ffcd9836904ef11230afce03ab5eaac5958b538382195b5aea9bf057c0486079869bb72ef9c958f33f1ed","0339081c4a0ce22c01aa78a5d025e7a109100d1a35ef0f8f06a0d4c5f9ffefc042","0339ea71aba2805238e636c2f1b3e5a8308b1dbdbb335787c51f2f6bf3f6218643","02ba4117a24d7e38ae14c429fce0d521aa1fb6bb97558a13f1ef2bc0a476a1741f","028cda4a0f03cbcbc695d9cac0858081fd5458acfd29564127d329553245afca42","04e7dc460c87267cf0958d6904d9cd99a4af0d64d61858636aec7a02e5f9a578d27c1329d5ddc45a937130ed4a59e4147cb4907724321baa6a976f9972a17f79ba","04f8cbd67830ab37138c92898a64a4edf836a60aa5b36956547788bd205c635d6a3056fa6a079961384ae336e737d4c45835821c8915dbc5e18a7def88df83946b"]},"seed_version":13,"stored_height":492756,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_8_3_seeded(self): - wallet_str = '{"addr_history":{"13sNgoAhqDUTB3YSzWYcKKvP2EczG5JGmt":[],"14C6nXs2GRaK3o5U5e8dJSpVRCoqTsyAkJ":[],"14fH7oRM4bqJtJkgJEynTShcUXQwdxH6mw":[],"16FECc7nP2wor1ijXKihGofUoCkoJnq6XR":[],"16cMJC5ZAtPnvLQBzfHm9YR9GoDxUseMEk":[],"17CbQhK3gutqgWt2iLX69ZeSCvw8yFxPLz":[],"17jEaAyekE8BHPvPmkJqFUh1v1GSi6ywoV":[],"19F5SjaWYVCKMPWR8q1Freo4RGChSmFztL":[],"19snysSPZEbgjmeMtuT7qDMTLH2fa7zrWW":[],"1AFgvLGNHP3nZDNrZ4R2BZKnbwDVAEUP4q":[],"1AwWgUbjQfRhKVLKm1o7qfpXnqeN3cu7Ms":[],"1B4FU2WEd2NQzd2MkWBLHw87uJhBxoVghh":[],"1BEBouVJFihDmEQMTAv4bNV2Q7dZh5iJzv":[],"1BdB7ahc8TSR9RJDmWgGSgsWji2BgzcVvC":[],"1DGhQ1up6dMieEwFdsQQFHRriyyR59rYVq":[],"1HBAAqFVndXBcWdWQNYVYSDK9kdUu8ZRU3":[],"1HMrRJkTayNRBZdXZKVb7oLZKj24Pq65T6":[],"1HiB2QCfNem8b4cJaZ2Rt9T4BbUCPXvTpT":[],"1HkbtbyocwHWjKBmzKmq8szv3cFgSGy7dL":[],"1K5CWjgZEYcKTsJWeQrH6NcMPzFUAikD8z":[],"1KMDUXdqpthH1XZU4q5kdSoMZmCW9yDMcN":[],"1KmHNiNmeS7tWRLYTFDMrTbKR6TERYicst":[],"1NQwmHYdxU1pFTTWyptn8vPW1hsSWJBRTn":[],"1NuPofeK8yNEjtVAu9Rc2pKS9kw8YWUatL":[],"1Q3eTNJWTnfxPkUJXQkeCqPh1cBQjjEXFn":[],"1QEuVTdenchPn9naMhakYx8QwGUXE6JYp":[]},"addresses":{"change":["1K5CWjgZEYcKTsJWeQrH6NcMPzFUAikD8z","19snysSPZEbgjmeMtuT7qDMTLH2fa7zrWW","1DGhQ1up6dMieEwFdsQQFHRriyyR59rYVq","17CbQhK3gutqgWt2iLX69ZeSCvw8yFxPLz","1Q3eTNJWTnfxPkUJXQkeCqPh1cBQjjEXFn","17jEaAyekE8BHPvPmkJqFUh1v1GSi6ywoV"],"receiving":["1KMDUXdqpthH1XZU4q5kdSoMZmCW9yDMcN","1HkbtbyocwHWjKBmzKmq8szv3cFgSGy7dL","1HiB2QCfNem8b4cJaZ2Rt9T4BbUCPXvTpT","14fH7oRM4bqJtJkgJEynTShcUXQwdxH6mw","1NuPofeK8yNEjtVAu9Rc2pKS9kw8YWUatL","16FECc7nP2wor1ijXKihGofUoCkoJnq6XR","19F5SjaWYVCKMPWR8q1Freo4RGChSmFztL","1NQwmHYdxU1pFTTWyptn8vPW1hsSWJBRTn","1HBAAqFVndXBcWdWQNYVYSDK9kdUu8ZRU3","1B4FU2WEd2NQzd2MkWBLHw87uJhBxoVghh","1HMrRJkTayNRBZdXZKVb7oLZKj24Pq65T6","1KmHNiNmeS7tWRLYTFDMrTbKR6TERYicst","1BdB7ahc8TSR9RJDmWgGSgsWji2BgzcVvC","14C6nXs2GRaK3o5U5e8dJSpVRCoqTsyAkJ","1AFgvLGNHP3nZDNrZ4R2BZKnbwDVAEUP4q","13sNgoAhqDUTB3YSzWYcKKvP2EczG5JGmt","1AwWgUbjQfRhKVLKm1o7qfpXnqeN3cu7Ms","1QEuVTdenchPn9naMhakYx8QwGUXE6JYp","1BEBouVJFihDmEQMTAv4bNV2Q7dZh5iJzv","16cMJC5ZAtPnvLQBzfHm9YR9GoDxUseMEk"]},"keystore":{"seed":"novel clay width echo swing blanket absorb salute asset under ginger final","type":"bip32","xprv":"xprv9s21ZrQH143K2jfFF6ektPj6zCCsDGGjQxhD2FQ21j6yrA1piWWEjch2kf1smzB2rzm8rPkdJuHf3vsKqMX9ogtE2A7JF49qVUHrgtjRymM","xpub":"xpub661MyMwAqRbcFDjiM8BmFXfqYE3McizanBcopdoda4dxixLyG3pVHR1WbwgjLo9RL882KRfpfpxh7a7zXPogDdR4xj9TpJWJGsbwaodLSKe"},"pruned_txo":{},"seed_type":"standard","seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' + wallet_str = '{"addr_history":{"t1Ljyh8aqoYG3mgbLvwMjT92JGtp54Q48qr":[],"t1M4hnsHAEkMueS8N24wkSFvQfrzvBLQzvh":[],"t1MXt88qV2vcuUwoaEfnubFoXjBc2Wnxhro":[],"t1P7qCwXvMMjQSemdTkXpQcmQ3rwt833RcA":[],"t1PUxJXVh9DBPWyT5w66tHMX4XTR3Bcj3Bg":[],"t1Q5CR2jBfEgSH9vvemLDHNkMTb8DpfDsR1":[],"t1QbqaWPniYumt2yHiB7xPHnwAfTXaaMMJo":[],"t1S7gT4zeWoyux2ZK5FpNzTtyfvPnEuUXDv":[],"t1SkPzCrXXZPHLQhFqLGEy2TNawDkKBVgY1":[],"t1T8HvfgWFhqP9rRkVVE9KNRhrbQZxGtc71":[],"t1Tp7gp1sNzDHv8PDhScEyUvT3VqSpz6cjj":[],"t1TvrUMvNbMA1bG5FgvzTRkE39xtGoxsXvA":[],"t1U6npEuSE3UpMsTFPbjBjBawempeVffJUL":[],"t1UVn7v7k6nE1k4M7hwVPaVyRzNDGVCFMGz":[],"t1W9JQMKx4x9KEsz9aJDXP6XmyeAVtNKtzz":[],"t1a3mBAfdkxJnD9gQLoMcgFKEQQpZeSPhmG":[],"t1aETReAbZJA1nCgRVkJiFcSUaPD9DTnEaR":[],"t1aan2jcoLyYjBhfCWyqZ1xYySFfH8AaXiD":[],"t1adCtwPwbG57KxEfvkaxGh6qJGSmDffgLm":[],"t1bwoX56hCsPv4WMQaqfQEBiGeeSYy8m5f8":[],"t1cDpUs3yoDUscAcN1FtsmFuGpRPax6tuMp":[],"t1cdtP3nuckuV74PSPg2UzGhEfkeKB23oPq":[],"t1fHYmcxmvnoQr6WQvFhuGjVRGN4XMqfudc":[],"t1fmzp14T7J9qLXY4qaEjAdRMQR8DMtK2UN":[],"t1gvFThieS7TYzPXCTqZmLeVcGGNVYgvzPF":[],"t1JGqupsmd7QHzRCgWnWhtN43fbTZHbiVkE":[]},"addresses":{"change":["t1bwoX56hCsPv4WMQaqfQEBiGeeSYy8m5f8","t1SkPzCrXXZPHLQhFqLGEy2TNawDkKBVgY1","t1W9JQMKx4x9KEsz9aJDXP6XmyeAVtNKtzz","t1Q5CR2jBfEgSH9vvemLDHNkMTb8DpfDsR1","t1gvFThieS7TYzPXCTqZmLeVcGGNVYgvzPF","t1QbqaWPniYumt2yHiB7xPHnwAfTXaaMMJo"],"receiving":["t1cDpUs3yoDUscAcN1FtsmFuGpRPax6tuMp","t1adCtwPwbG57KxEfvkaxGh6qJGSmDffgLm","t1aan2jcoLyYjBhfCWyqZ1xYySFfH8AaXiD","t1MXt88qV2vcuUwoaEfnubFoXjBc2Wnxhro","t1fmzp14T7J9qLXY4qaEjAdRMQR8DMtK2UN","t1P7qCwXvMMjQSemdTkXpQcmQ3rwt833RcA","t1S7gT4zeWoyux2ZK5FpNzTtyfvPnEuUXDv","t1fHYmcxmvnoQr6WQvFhuGjVRGN4XMqfudc","t1a3mBAfdkxJnD9gQLoMcgFKEQQpZeSPhmG","t1TvrUMvNbMA1bG5FgvzTRkE39xtGoxsXvA","t1aETReAbZJA1nCgRVkJiFcSUaPD9DTnEaR","t1cdtP3nuckuV74PSPg2UzGhEfkeKB23oPq","t1UVn7v7k6nE1k4M7hwVPaVyRzNDGVCFMGz","t1M4hnsHAEkMueS8N24wkSFvQfrzvBLQzvh","t1T8HvfgWFhqP9rRkVVE9KNRhrbQZxGtc71","t1Ljyh8aqoYG3mgbLvwMjT92JGtp54Q48qr","t1Tp7gp1sNzDHv8PDhScEyUvT3VqSpz6cjj","t1JGqupsmd7QHzRCgWnWhtN43fbTZHbiVkE","t1U6npEuSE3UpMsTFPbjBjBawempeVffJUL","t1PUxJXVh9DBPWyT5w66tHMX4XTR3Bcj3Bg"]},"keystore":{"seed":"novel clay width echo swing blanket absorb salute asset under ginger final","type":"bip32","xprv":"xprv9s21ZrQH143K2jfFF6ektPj6zCCsDGGjQxhD2FQ21j6yrA1piWWEjch2kf1smzB2rzm8rPkdJuHf3vsKqMX9ogtE2A7JF49qVUHrgtjRymM","xpub":"xpub661MyMwAqRbcFDjiM8BmFXfqYE3McizanBcopdoda4dxixLyG3pVHR1WbwgjLo9RL882KRfpfpxh7a7zXPogDdR4xj9TpJWJGsbwaodLSKe"},"pruned_txo":{},"seed_type":"standard","seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_8_3_importedkeys(self): - wallet_str = '{"addr_history":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":[],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":[],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":[]},"addresses":{"change":[],"receiving":["1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr","1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6","15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA"]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' + wallet_str = '{"addr_history":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":[],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":[],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":[]},"addresses":{"change":[],"receiving":["t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn","t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM","t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T"]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_8_3_watchaddresses(self): - wallet_str = '{"addr_history":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[]},"addresses":["1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs","1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa","1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf"],"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"verified_tx3":{},"wallet_type":"imported","winpos-qt":[535,380,840,405]}' + wallet_str = '{"addr_history":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[]},"addresses":["t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq","t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu","t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j"],"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"verified_tx3":{},"wallet_type":"imported","winpos-qt":[535,380,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_8_3_trezor_singleacc(self): - wallet_str = '''{"addr_history":{"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC":[],"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz":[],"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM":[],"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ":[],"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6":[],"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S":[],"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH":[],"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw":[],"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb":[],"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX":[],"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ":[],"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp":[],"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk":[],"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD":[],"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp":[],"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz":[],"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs":[],"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid":[],"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu":[],"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo":[],"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj":[],"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv":[],"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC":[],"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe":[],"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL":[],"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG":[]},"addresses":{"change":["1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ","14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM","1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG","15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6","1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL","1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs"],"receiving":["1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu","18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw","17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH","12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC","15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ","1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid","1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz","1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj","146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz","1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC","1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo","1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb","1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe","1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv","1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp","15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S","1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX","1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp","1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk","1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD"]},"keystore":{"derivation":"m/44'/0'/0'","hw_type":"trezor","label":"trezor1","type":"hardware","xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[744,390,840,405]}''' + wallet_str = '''{"addr_history":{"t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy":[],"t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu":[],"t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5":[],"t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM":[],"t1Njrm3uzEuQg1MNapyYnpXEMhJRkCAxc1d":[],"t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce":[],"t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ":[],"t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49":[],"t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw":[],"t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk":[],"t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq":[],"t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P":[],"t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C":[],"t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i":[],"t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth":[],"t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby":[],"t1cgVBNfdDwLVsoYXMnymANTvc5RuFGLm1w":[],"t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF":[],"t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE":[],"t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi":[],"t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn":[],"t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8":[],"t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM":[],"t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R":[],"t1fSU5JTkH2hM6GNLUeWhrAPcdikYsBwgdX":[],"t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya":[]},"addresses":{"change":["t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq","t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5","t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya","t1Njrm3uzEuQg1MNapyYnpXEMhJRkCAxc1d","t1fSU5JTkH2hM6GNLUeWhrAPcdikYsBwgdX","t1cgVBNfdDwLVsoYXMnymANTvc5RuFGLm1w"],"receiving":["t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE","t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49","t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ","t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy","t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM","t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF","t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby","t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn","t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu","t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM","t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi","t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw","t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R","t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8","t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth","t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce","t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk","t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P","t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C","t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i"]},"keystore":{"derivation":"m/44'/0'/0'","hw_type":"trezor","label":"trezor1","type":"hardware","xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[744,390,840,405]}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_8_3_multisig(self): - wallet_str = '{"addr_history":{"32Qk6Q7XYD2v3et9g5fA97ky8XRAJNDZCS":[],"339axnadPaQg3ngChNBKap2dndUWrSwjk6":[],"34FG8qzA6UYLxrnkpVkM9mrGYix3ZyePJZ":[],"35CR3h2dFF3EkRX5yK47NGuF2FcLtJvpUM":[],"35zrocLBQbHfEqysgv2v5z3RH7BRGQzSMJ":[],"36uBJPkgiQwav23ybewbgkQ2zEzJDY2EX1":[],"37nSiBvGXm1PNYseymaJn5ERcU4mSMueYc":[],"39r4XCmfU4J3N98YQ8Fwvm8VN1Fukfj7QW":[],"3BDqFoYMxyy7nWCpRChYV6YCGh9qnWDmav":[],"3CGCLSHU8ZjeXv6oukJ3eAQN4fqEQ7wuyX":[],"3DCNnfh7oWLsnS3p5QdWfW3hvcFF8qAPFq":[],"3DPheE9uany9ET2qBnWF1wh3zDtptGP6Ts":[],"3EeNJHgSYVJPxYR2NaYv2M2ZnXkPRWSHQh":[],"3FWZ7pJPxZhGr8p6HNr9LLsHA8sABcP7cF":[],"3FZbzEF9HdRqzif2cKUFnwW9AFTJcibjVK":[],"3GEhQHTrWykC6Jfu923qtpxJECsEGVdhUc":[],"3HJ95uxwW6rMoEhYgUfcgpd3ExU3fjkfNb":[],"3HbdMVgKRqadNiHRNGizUCyTQYpJ1aXFav":[],"3J6xRF9d16QNsvoXkYkeTwTU8L5N3Y8f7c":[],"3JBbS3GvhvoLgtLcuMvHCtqjE7dnbpTMkz":[],"3KNWZasWDBuVzzp5Y5cbEgjeYn3NKHZKso":[],"3KQ5tTEbkQSkKiccKFDPrhLnBjSMey6CQM":[],"3KrFHcAzNJYjukGDDZm2HeV5Mok4NGQaD6":[],"3LNZbX9wenL3bLxJTQnPidSvVt3EtDrnUg":[],"3LzjAqqfiN8w4TSiW8Up7bKLD5CicBUC3a":[],"3Nro51wauHugv72NMtY9pmLnwX3FXWU1eE":[]},"addresses":{"change":["34FG8qzA6UYLxrnkpVkM9mrGYix3ZyePJZ","3LzjAqqfiN8w4TSiW8Up7bKLD5CicBUC3a","3GEhQHTrWykC6Jfu923qtpxJECsEGVdhUc","3Nro51wauHugv72NMtY9pmLnwX3FXWU1eE","3JBbS3GvhvoLgtLcuMvHCtqjE7dnbpTMkz","3CGCLSHU8ZjeXv6oukJ3eAQN4fqEQ7wuyX"],"receiving":["35zrocLBQbHfEqysgv2v5z3RH7BRGQzSMJ","3FWZ7pJPxZhGr8p6HNr9LLsHA8sABcP7cF","3DPheE9uany9ET2qBnWF1wh3zDtptGP6Ts","3HbdMVgKRqadNiHRNGizUCyTQYpJ1aXFav","3KQ5tTEbkQSkKiccKFDPrhLnBjSMey6CQM","35CR3h2dFF3EkRX5yK47NGuF2FcLtJvpUM","3HJ95uxwW6rMoEhYgUfcgpd3ExU3fjkfNb","3FZbzEF9HdRqzif2cKUFnwW9AFTJcibjVK","39r4XCmfU4J3N98YQ8Fwvm8VN1Fukfj7QW","3LNZbX9wenL3bLxJTQnPidSvVt3EtDrnUg","32Qk6Q7XYD2v3et9g5fA97ky8XRAJNDZCS","339axnadPaQg3ngChNBKap2dndUWrSwjk6","3EeNJHgSYVJPxYR2NaYv2M2ZnXkPRWSHQh","3BDqFoYMxyy7nWCpRChYV6YCGh9qnWDmav","3DCNnfh7oWLsnS3p5QdWfW3hvcFF8qAPFq","3KNWZasWDBuVzzp5Y5cbEgjeYn3NKHZKso","37nSiBvGXm1PNYseymaJn5ERcU4mSMueYc","3KrFHcAzNJYjukGDDZm2HeV5Mok4NGQaD6","36uBJPkgiQwav23ybewbgkQ2zEzJDY2EX1","3J6xRF9d16QNsvoXkYkeTwTU8L5N3Y8f7c"]},"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"2of2","winpos-qt":[671,238,840,405],"x1/":{"seed":"property play install hill hunt follow trash comic pulse consider canyon limit","type":"bip32","xprv":"xprv9s21ZrQH143K46tCjDh5i4H9eSJpnMrYyLUbVZheTbNjiamdxPiffMEYLgxuYsMFokFrNEZ6S6z5wSXXszXaCVQWf6jzZvn14uYZhsnM9Sb","xpub":"xpub661MyMwAqRbcGaxfqFE65CDtCU9KBpaQLZQCHx7G1vuibP6nVw2vD9Z2Bz2DsH43bDZGXjmcvx2TD9wq3CmmFcoT96RCiDd1wMSUB2UH7Gu"},"x2/":{"type":"bip32","xprv":null,"xpub":"xpub661MyMwAqRbcEncvVc1zrPFZSKe7iAP1LTRhzxuXpmztu1kTtnfj8XNFzzmGH1X1gcGxczBZ3MmYKkxXgZKJCsNXXdasNaQJKJE4KcUjn1L"}}' + wallet_str = '{"addr_history":{"t3KHM6jXfWXpWeHw3cWUHGvrtPBcF6VDVJ2":[],"t3L2By7zmMuCGeRj6dnzSid8Z3Hfbb1ymUr":[],"t3M7s9BQJ4oKwZVqekvZUHaxBoP98H5NEXT":[],"t3N5242SmDZpqM4ZyujsEW61AGuoRkDCb82":[],"t3NsTowkKNv5FqV2mdLr3Do9LXmNW2WDFC2":[],"t3PmnJjApgjjBWf6sY5kipZVxEuBP1Pbp1y":[],"t3Qf3iXLQW5nyyBvYvCPRutLLs8FrDczsFa":[],"t3SifXYBoSP5dxnBSLZ554aEQcfSzZuowYs":[],"t3U6SG8xVwJkiP9FiMdWfcue7XMLvdrESJm":[],"t3V8oLmhc6tXF8Z9hrB7AmyWHKL2K8gqQeV":[],"t3W4yo17Fmq8UP56i1qSdoK9dBGSKwcNCZm":[],"t3WGJeZa3Z7kjq65j8DKN9knyEt5uio89QG":[],"t3XWyJd6aWp5zZBTvK1N3AA8V3BwUFjN3vx":[],"t3YPA89iXvtUsSmrzDofGU9yCQo4EyuAtev":[],"t3YSCzZfHFxDSbMhvYkHNvkc4QuePQYbToh":[],"t3Z7JQcszVJXngwio5Sry2e4DUs4K1iV2wV":[],"t3aAk6FP5URdxPskScuUjpdixVcf8RQNk2G":[],"t3aUEMq6TQANDyMLKJhY7c25NfD1No29Qak":[],"t3ayZRaZkyRByUZrRgyZmbkZPNzGSoHM7s8":[],"t3b4CSNh4gFawHXPWqnjQLhweUmpsSF8qNv":[],"t3cF7ZvHeBWh6bdryUWRiNVqZoSET4iannG":[],"t3cGgtnejijELvMfWFg2WzWShSPdSU5ZvWE":[],"t3cirHwb8LdLLWPK79za9RTazcTw9Auwkz7":[],"t3dFAbra5d77eBz1CPqbWrSYqkYEKjeVaFB":[],"t3dsLBBFoggvXf6VcSZHwFQRFTjPoPvTRY6":[],"t3fjQ5MMischHWk5GJKMGxaSiCBELMEwpd7":[]},"addresses":{"change":["t3M7s9BQJ4oKwZVqekvZUHaxBoP98H5NEXT","t3dsLBBFoggvXf6VcSZHwFQRFTjPoPvTRY6","t3Z7JQcszVJXngwio5Sry2e4DUs4K1iV2wV","t3fjQ5MMischHWk5GJKMGxaSiCBELMEwpd7","t3b4CSNh4gFawHXPWqnjQLhweUmpsSF8qNv","t3V8oLmhc6tXF8Z9hrB7AmyWHKL2K8gqQeV"],"receiving":["t3NsTowkKNv5FqV2mdLr3Do9LXmNW2WDFC2","t3YPA89iXvtUsSmrzDofGU9yCQo4EyuAtev","t3WGJeZa3Z7kjq65j8DKN9knyEt5uio89QG","t3aUEMq6TQANDyMLKJhY7c25NfD1No29Qak","t3cGgtnejijELvMfWFg2WzWShSPdSU5ZvWE","t3N5242SmDZpqM4ZyujsEW61AGuoRkDCb82","t3aAk6FP5URdxPskScuUjpdixVcf8RQNk2G","t3YSCzZfHFxDSbMhvYkHNvkc4QuePQYbToh","t3SifXYBoSP5dxnBSLZ554aEQcfSzZuowYs","t3dFAbra5d77eBz1CPqbWrSYqkYEKjeVaFB","t3KHM6jXfWXpWeHw3cWUHGvrtPBcF6VDVJ2","t3L2By7zmMuCGeRj6dnzSid8Z3Hfbb1ymUr","t3XWyJd6aWp5zZBTvK1N3AA8V3BwUFjN3vx","t3U6SG8xVwJkiP9FiMdWfcue7XMLvdrESJm","t3W4yo17Fmq8UP56i1qSdoK9dBGSKwcNCZm","t3cF7ZvHeBWh6bdryUWRiNVqZoSET4iannG","t3Qf3iXLQW5nyyBvYvCPRutLLs8FrDczsFa","t3cirHwb8LdLLWPK79za9RTazcTw9Auwkz7","t3PmnJjApgjjBWf6sY5kipZVxEuBP1Pbp1y","t3ayZRaZkyRByUZrRgyZmbkZPNzGSoHM7s8"]},"pruned_txo":{},"seed_version":13,"stored_height":0,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"2of2","winpos-qt":[671,238,840,405],"x1/":{"seed":"property play install hill hunt follow trash comic pulse consider canyon limit","type":"bip32","xprv":"xprv9s21ZrQH143K46tCjDh5i4H9eSJpnMrYyLUbVZheTbNjiamdxPiffMEYLgxuYsMFokFrNEZ6S6z5wSXXszXaCVQWf6jzZvn14uYZhsnM9Sb","xpub":"xpub661MyMwAqRbcGaxfqFE65CDtCU9KBpaQLZQCHx7G1vuibP6nVw2vD9Z2Bz2DsH43bDZGXjmcvx2TD9wq3CmmFcoT96RCiDd1wMSUB2UH7Gu"},"x2/":{"type":"bip32","xprv":null,"xpub":"xpub661MyMwAqRbcEncvVc1zrPFZSKe7iAP1LTRhzxuXpmztu1kTtnfj8XNFzzmGH1X1gcGxczBZ3MmYKkxXgZKJCsNXXdasNaQJKJE4KcUjn1L"}}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_9_3_seeded(self): - wallet_str = '{"addr_history":{"12ECgkzK6gHouKAZ7QiooYBuk1CgJLJxes":[],"12iR43FPb5M7sw4Mcrr5y1nHKepg9EtZP1":[],"13HT1pfWctsSXVFzF76uYuVdQvcAQ2MAgB":[],"13kG9WH9JqS7hyCcVL1ssLdNv4aXocQY9c":[],"14Tf3qiiHJXStSU4KmienAhHfHq7FHpBpz":[],"14gmBxYV97mzYwWdJSJ3MTLbTHVegaKrcA":[],"15FGuHvRssu1r8fCw98vrbpfc3M4xs5FAV":[],"17oJzweA2gn6SDjsKgA9vUD5ocT1sSnr2Z":[],"18hNcSjZzRcRP6J2bfFRxp9UfpMoC4hGTv":[],"18n9PFxBjmKCGhd4PCDEEqYsi2CsnEfn2B":[],"19a98ZfEezDNbCwidVigV5PAJwrR2kw4Jz":[],"19z3j2ELqbg2pR87byCCt3BCyKR7rc3q8G":[],"1A3XSmvLQvePmvm7yctsGkBMX9ZKKXLrVq":[],"1CmhFe2BN1h9jheFpJf4v39XNPj8F9U6d":[],"1DuphhHUayKzbkdvjVjf5dtjn2ACkz4zEs":[],"1E4ygSNJpWL2uPXZHBptmU2LqwZTqb1Ado":[],"1GTDSjkVc9vaaBBBGNVqTANHJBcoT5VW9z":[],"1GWqgpThAuSq3tDg6uCoLQxPXQNnU8jZ52":[],"1GhmpwqSF5cqNgdr9oJMZx8dKxPRo4pYPP":[],"1J5TTUQKhwehEACw6Jjte1E22FVrbeDmpv":[],"1JWySzjzJhsETUUcqVZHuvQLA7pfFfmesb":[],"1KQHxcy3QUHAWMHKUtJjqD9cMKXcY2RTwZ":[],"1KoxZfc2KsgovjGDxwqanbFEA76uxgYH4G":[],"1KqVEPXdpbYvEbwsZcEKkrA4A2jsgj9hYN":[],"1N16yDSYe76c5A3CoVoWAKxHeAUc8Jhf9J":[],"1Pm8JBhzUJDqeQQKrmnop1Frr4phe1jbTt":[]},"addresses":{"change":["1GhmpwqSF5cqNgdr9oJMZx8dKxPRo4pYPP","1GTDSjkVc9vaaBBBGNVqTANHJBcoT5VW9z","15FGuHvRssu1r8fCw98vrbpfc3M4xs5FAV","1A3XSmvLQvePmvm7yctsGkBMX9ZKKXLrVq","19z3j2ELqbg2pR87byCCt3BCyKR7rc3q8G","1JWySzjzJhsETUUcqVZHuvQLA7pfFfmesb"],"receiving":["14gmBxYV97mzYwWdJSJ3MTLbTHVegaKrcA","13HT1pfWctsSXVFzF76uYuVdQvcAQ2MAgB","19a98ZfEezDNbCwidVigV5PAJwrR2kw4Jz","1J5TTUQKhwehEACw6Jjte1E22FVrbeDmpv","1Pm8JBhzUJDqeQQKrmnop1Frr4phe1jbTt","13kG9WH9JqS7hyCcVL1ssLdNv4aXocQY9c","1KQHxcy3QUHAWMHKUtJjqD9cMKXcY2RTwZ","12ECgkzK6gHouKAZ7QiooYBuk1CgJLJxes","12iR43FPb5M7sw4Mcrr5y1nHKepg9EtZP1","14Tf3qiiHJXStSU4KmienAhHfHq7FHpBpz","1KqVEPXdpbYvEbwsZcEKkrA4A2jsgj9hYN","17oJzweA2gn6SDjsKgA9vUD5ocT1sSnr2Z","1E4ygSNJpWL2uPXZHBptmU2LqwZTqb1Ado","18hNcSjZzRcRP6J2bfFRxp9UfpMoC4hGTv","1KoxZfc2KsgovjGDxwqanbFEA76uxgYH4G","18n9PFxBjmKCGhd4PCDEEqYsi2CsnEfn2B","1CmhFe2BN1h9jheFpJf4v39XNPj8F9U6d","1DuphhHUayKzbkdvjVjf5dtjn2ACkz4zEs","1GWqgpThAuSq3tDg6uCoLQxPXQNnU8jZ52","1N16yDSYe76c5A3CoVoWAKxHeAUc8Jhf9J"]},"keystore":{"seed":"cereal wise two govern top pet frog nut rule sketch bundle logic","type":"bip32","xprv":"xprv9s21ZrQH143K29XjRjUs6MnDB9wXjXbJP2kG1fnRk8zjdDYWqVkQYUqaDtgZp5zPSrH5PZQJs8sU25HrUgT1WdgsPU8GbifKurtMYg37d4v","xpub":"xpub661MyMwAqRbcEdcCXm1sTViwjBn28zK9kFfrp4C3JUXiW1sfP34f6HA45B9yr7EH5XGzWuTfMTdqpt9XPrVQVUdgiYb5NW9m8ij1FSZgGBF"},"pruned_txo":{},"seed_type":"standard","seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[619,310,840,405]}' + wallet_str = '{"addr_history":{"t1K6oh6QT515QVxDT3qXvwMHpzfPm6cBNRi":[],"t1Kb24NfXZQ8iUa7FZHfD6ptCaK1kxZSfFT":[],"t1LA42A5ebDf388JtBXv2gibYfaoFB4NuCN":[],"t1Lcs9qhHHADiJcFWRkq119jJAimcfLJj3M":[],"t1MLG4B8rFdK3V5WxGCXmuyoCux2C4TYL6o":[],"t1MZNCHxd7SZb9aZXEs7AVGSWhwgjPoyHKy":[],"t1N7sudLZrCgcSmi6sZx3zQvarhY9hooRuc":[],"t1Qfv1H4J11Zh2rnmG6yH4HK14Ge6diB1xf":[],"t1RZycn9hxkQ1yjLvY64Z6dFPvUYsyL4N4h":[],"t1RekPbNKi66nsLfxKd2MNeenxgPxYosVQe":[],"t1SSk8u5NdJzyBqzcZvXoctV5Zc3Vm9JWgr":[],"t1SrejMeUovTdR4B1YQ1L1rH8DycCgGvXw7":[],"t1Sv8T7LUPFRzNZp1v3hzQZHGmokQ7WJfh9":[],"t1J5Nhb4A9goHkNkYCF7nCj94n2aowBe767":[],"t1WnRi2hcZJ7bCPgpfvYnDSzf2gMHYgj61X":[],"t1WwagmnSnq7dW2aTDce1uH8G6bkYese4ea":[],"t1ZKpT5AdaUiBApE5CoJxayUCYqotAuVAbX":[],"t1ZPSh9sq9EEReXGa3L1vUE4Jn4ZsGZpYeU":[],"t1ZaNqHFaDQQRyKgk6E7UhmEYacaWaRKBNg":[],"t1ax4TopTgGSHpoFq2jZ1mpKwGugwNuA9dq":[],"t1bPaTLA8H2eq47XWmvNR3jWFQn1jxboBjQ":[],"t1cGtxxPBNo4m6zLDRK7ry2FXbyihKqPe51":[],"t1cgZa12AJCUQXNK7uNehvQM9QmHzqXYo9r":[],"t1ci6EiwmnvLWqEzmW33StfFyQgvxXwxDWM":[],"t1eshyYrgcRtCfo66jvcdJ94CtpfgqWB1bA":[],"t1gdjJX88Sd1SF3TDoCbvwpMn6j1nWhjo23":[]},"addresses":{"change":["t1ZaNqHFaDQQRyKgk6E7UhmEYacaWaRKBNg","t1ZKpT5AdaUiBApE5CoJxayUCYqotAuVAbX","t1N7sudLZrCgcSmi6sZx3zQvarhY9hooRuc","t1Sv8T7LUPFRzNZp1v3hzQZHGmokQ7WJfh9","t1SrejMeUovTdR4B1YQ1L1rH8DycCgGvXw7","t1bPaTLA8H2eq47XWmvNR3jWFQn1jxboBjQ"],"receiving":["t1MZNCHxd7SZb9aZXEs7AVGSWhwgjPoyHKy","t1LA42A5ebDf388JtBXv2gibYfaoFB4NuCN","t1SSk8u5NdJzyBqzcZvXoctV5Zc3Vm9JWgr","t1ax4TopTgGSHpoFq2jZ1mpKwGugwNuA9dq","t1gdjJX88Sd1SF3TDoCbvwpMn6j1nWhjo23","t1Lcs9qhHHADiJcFWRkq119jJAimcfLJj3M","t1cGtxxPBNo4m6zLDRK7ry2FXbyihKqPe51","t1K6oh6QT515QVxDT3qXvwMHpzfPm6cBNRi","t1Kb24NfXZQ8iUa7FZHfD6ptCaK1kxZSfFT","t1MLG4B8rFdK3V5WxGCXmuyoCux2C4TYL6o","t1ci6EiwmnvLWqEzmW33StfFyQgvxXwxDWM","t1Qfv1H4J11Zh2rnmG6yH4HK14Ge6diB1xf","t1WwagmnSnq7dW2aTDce1uH8G6bkYese4ea","t1RZycn9hxkQ1yjLvY64Z6dFPvUYsyL4N4h","t1cgZa12AJCUQXNK7uNehvQM9QmHzqXYo9r","t1RekPbNKi66nsLfxKd2MNeenxgPxYosVQe","t1J5Nhb4A9goHkNkYCF7nCj94n2aowBe767","t1WnRi2hcZJ7bCPgpfvYnDSzf2gMHYgj61X","t1ZPSh9sq9EEReXGa3L1vUE4Jn4ZsGZpYeU","t1eshyYrgcRtCfo66jvcdJ94CtpfgqWB1bA"]},"keystore":{"seed":"cereal wise two govern top pet frog nut rule sketch bundle logic","type":"bip32","xprv":"xprv9s21ZrQH143K29XjRjUs6MnDB9wXjXbJP2kG1fnRk8zjdDYWqVkQYUqaDtgZp5zPSrH5PZQJs8sU25HrUgT1WdgsPU8GbifKurtMYg37d4v","xpub":"xpub661MyMwAqRbcEdcCXm1sTViwjBn28zK9kFfrp4C3JUXiW1sfP34f6HA45B9yr7EH5XGzWuTfMTdqpt9XPrVQVUdgiYb5NW9m8ij1FSZgGBF"},"pruned_txo":{},"seed_type":"standard","seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[619,310,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_9_3_importedkeys(self): - wallet_str = '{"addr_history":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":[],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":[],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":[]},"addresses":{"change":[],"receiving":["1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr","1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6","15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA"]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' + wallet_str = '{"addr_history":{"t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn":[],"t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T":[],"t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM":[]},"addresses":{"change":[],"receiving":["t1KxfKCSdEQsnY4geXEmHi4zYeFCjFBq9Vn","t1XqFtMbqGC3Yv6zbj6SzKArJnzU1e2zZxM","t1N5aE1koddeuH3ubZQcvP6Ac39QLr5HZ9T"]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_9_3_watchaddresses(self): - wallet_str = '{"addr_history":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[]},"addresses":["1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs","1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa","1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf"],"pruned_txo":{},"seed_version":13,"stored_height":490039,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"verified_tx3":{},"wallet_type":"imported","winpos-qt":[499,386,840,405]}' + wallet_str = '{"addr_history":{"t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j":[],"t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq":[],"t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu":[]},"addresses":["t1ZvNPrhPxUv5CZkJS1QjMYerHrafrSUQuq","t1agDPyKRjjFuFVsXWm5FMbstBSQHHKzAzu","t1WZTwhSSAYLhW2QLrsoFmCkQ9F9RhEfL7j"],"pruned_txo":{},"seed_version":13,"stored_height":490039,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"verified_tx3":{},"wallet_type":"imported","winpos-qt":[499,386,840,405]}' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_9_3_trezor_singleacc(self): - wallet_str = '''{"addr_history":{"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC":[],"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz":[],"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM":[],"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ":[],"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6":[],"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S":[],"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH":[],"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw":[],"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb":[],"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX":[],"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ":[],"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp":[],"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk":[],"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD":[],"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp":[],"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz":[],"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs":[],"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid":[],"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu":[],"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo":[],"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj":[],"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv":[],"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC":[],"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe":[],"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL":[],"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG":[]},"addresses":{"change":["1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ","14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM","1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG","15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6","1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL","1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs"],"receiving":["1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu","18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw","17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH","12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC","15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ","1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid","1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz","1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj","146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz","1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC","1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo","1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb","1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe","1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv","1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp","15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S","1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX","1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp","1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk","1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD"]},"keystore":{"derivation":"m/44'/0'/0'","hw_type":"trezor","label":"trezor1","type":"hardware","xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"pruned_txo":{},"seed_version":13,"stored_height":490014,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[753,486,840,405]}''' + wallet_str = '''{"addr_history":{"t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy":[],"t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu":[],"t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5":[],"t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM":[],"t1Njrm3uzEuQg1MNapyYnpXEMhJRkCAxc1d":[],"t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce":[],"t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ":[],"t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49":[],"t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw":[],"t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk":[],"t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq":[],"t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P":[],"t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C":[],"t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i":[],"t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth":[],"t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby":[],"t1cgVBNfdDwLVsoYXMnymANTvc5RuFGLm1w":[],"t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF":[],"t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE":[],"t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi":[],"t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn":[],"t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8":[],"t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM":[],"t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R":[],"t1fSU5JTkH2hM6GNLUeWhrAPcdikYsBwgdX":[],"t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya":[]},"addresses":{"change":["t1J7393C2er2w2HzzwdNR2wBXaucd3dAvJq","t1M5Q2XqdsQu7vqKk9VEcddgZ9Tihcs2jo5","t1JipkcfwxjuLnkQrN9Pt5rs6u8CLkeEUya","t1Njrm3uzEuQg1MNapyYnpXEMhJRkCAxc1d","t1fSU5JTkH2hM6GNLUeWhrAPcdikYsBwgdX","t1cgVBNfdDwLVsoYXMnymANTvc5RuFGLm1w"],"receiving":["t1dh63aSDi3A75mQvMpC8kbRBwUveTb7zXE","t1RKvqDQvR2Mf5vPtkvQ1vfL6tYhqZopx49","t1QR1XshwCAQRDkKpSnxJg8EB2t44SLHJNQ","t1Kk1vpwpc8kczrrkH7fA1uQ3ZAC4Q48cTy","t1NBpqb7mVhtANhanEvJcpPoDNGu1urkHDM","t1dWfrrpM358sUShmzf6Fd66oL9kpQUtueF","t1cf1XQdDSEpVfr2QmLLhnAE8Ba6ZzuERby","t1eim1qrCgqQ4KyugPGBVxM6N5jhDkMHMWn","t1LyL6kmjV978qDKMZwK7hrpaJFyH2Ym4Yu","t1fEMF3KcXCk3oPij6Rn5LGuua2ZvaTCEnM","t1eWS8vuPQWTBEv8pg1LPsXVeDm2UJARfLi","t1UfHGWH5g9n7KqU8TyEZ9ioPXYBHHinnnw","t1fL2FTxFLDriE3CMCNHHGwLZ2enFMxuf3R","t1f8JYsbpzJzsERt6wbjvHVgQjnY1bhtRT8","t1atydn5Pgj8JLvuv9vnJtzA2cQHAeZ2kth","t1NsQPhVzTdzoTxqTRBGfSKMqKLXiPW4vce","t1X2YT4reia6QdvXURRHNHTcjTQfeXq5ZNk","t1YW68KjEPxCkvUiwZ35QQ8Aw2xXZjizX3P","t1ZbracP8odncxfJ17DsGsvfqbq1HWhaD8C","t1actDkerWNYk4r1UNbNMupzn4EEj35aU9i"]},"keystore":{"derivation":"m/44'/0'/0'","hw_type":"trezor","label":"trezor1","type":"hardware","xpub":"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y"},"pruned_txo":{},"seed_version":13,"stored_height":490014,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[753,486,840,405]}''' self._upgrade_storage(wallet_str) def test_upgrade_from_client_2_9_3_multisig(self): - wallet_str = '{"addr_history":{"31uiqKhw4PQSmZWnCkqpeh6moB8B1jXEt3":[],"32PBjkXmwRoEQt8HBZcAEUbNwaHw5dR5fe":[],"33FQMD675LMRLZDLYLK7QV6TMYA1uYW1sw":[],"33MQEs6TCgxmAJhZvUEXYr6gCkEoEYzUfm":[],"33vuhs2Wor9Xkax66ucDkscPcU6nQHw8LA":[],"35tbMt1qBGmy5RNcsdGZJgs7XVbf5gEgPs":[],"36zhHEtGA33NjHJdxCMjY6DLeU2qxhiLUE":[],"37rZuTsieKVpRXshwrY8qvFBn6me42mYr5":[],"38A2KDXYRmRKZRRCGgazrj19i22kDr8d4V":[],"38GZH5GhxLKi5so9Aka6orY2EDZkvaXdxm":[],"3AEtxrCwiYv5Y5CRmHn1c5nZnV3Hpfh5BM":[],"3AaHWprY1MytygvQVDLp6i63e9o5CwMSN5":[],"3DAD19hHXNxAfZjCtUbWjZVxw1fxQqCbY7":[],"3GK4CBbgwumoeR9wxJjr1QnfnYhGUEzHhN":[],"3H18xmkyX3XAb5MwucqKpEhTnh3qz8V4Mn":[],"3JhkakvHAyFvukJ3cyaVgiyaqjYNo2gmsS":[],"3JtA4x1AKW4BR5YAEeLR5D157Nd92NHArC":[],"3KQosfGFGsUniyqsidE2Y4Bz1y4iZUkGW6":[],"3KXe1z2Lfk22zL6ggQJLpHZfc9dKxYV95p":[],"3KZiENj4VHdUycv9UDts4ojVRsaMk8LC5c":[],"3KeTKHJbkZN1QVkvKnHRqYDYP7UXsUu6va":[],"3L5aZKtDKSd65wPLMRooNtWHkKd5Mz6E3i":[],"3LAPqjqW4C2Se9HNziUhNaJQS46X1r9p3M":[],"3P3JJPoyNFussuyxkDbnYevYim5XnPGmwZ":[],"3PgNdMYSaPRymskby885DgKoTeA1uZr6Gi":[],"3Pm7DaUzaDMxy2mW5WzHp1sE9hVWEpdf7J":[]},"addresses":{"change":["31uiqKhw4PQSmZWnCkqpeh6moB8B1jXEt3","3JhkakvHAyFvukJ3cyaVgiyaqjYNo2gmsS","3GK4CBbgwumoeR9wxJjr1QnfnYhGUEzHhN","3LAPqjqW4C2Se9HNziUhNaJQS46X1r9p3M","33MQEs6TCgxmAJhZvUEXYr6gCkEoEYzUfm","3AEtxrCwiYv5Y5CRmHn1c5nZnV3Hpfh5BM"],"receiving":["3P3JJPoyNFussuyxkDbnYevYim5XnPGmwZ","33FQMD675LMRLZDLYLK7QV6TMYA1uYW1sw","3DAD19hHXNxAfZjCtUbWjZVxw1fxQqCbY7","3AaHWprY1MytygvQVDLp6i63e9o5CwMSN5","3H18xmkyX3XAb5MwucqKpEhTnh3qz8V4Mn","36zhHEtGA33NjHJdxCMjY6DLeU2qxhiLUE","37rZuTsieKVpRXshwrY8qvFBn6me42mYr5","38A2KDXYRmRKZRRCGgazrj19i22kDr8d4V","38GZH5GhxLKi5so9Aka6orY2EDZkvaXdxm","33vuhs2Wor9Xkax66ucDkscPcU6nQHw8LA","3L5aZKtDKSd65wPLMRooNtWHkKd5Mz6E3i","3KXe1z2Lfk22zL6ggQJLpHZfc9dKxYV95p","3KQosfGFGsUniyqsidE2Y4Bz1y4iZUkGW6","3KZiENj4VHdUycv9UDts4ojVRsaMk8LC5c","32PBjkXmwRoEQt8HBZcAEUbNwaHw5dR5fe","3KeTKHJbkZN1QVkvKnHRqYDYP7UXsUu6va","3JtA4x1AKW4BR5YAEeLR5D157Nd92NHArC","3PgNdMYSaPRymskby885DgKoTeA1uZr6Gi","3Pm7DaUzaDMxy2mW5WzHp1sE9hVWEpdf7J","35tbMt1qBGmy5RNcsdGZJgs7XVbf5gEgPs"]},"pruned_txo":{},"seed_version":13,"stored_height":485855,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"2of2","winpos-qt":[617,227,840,405],"x1/":{"seed":"speed cruise market wasp ability alarm hold essay grass coconut tissue recipe","type":"bip32","xprv":"xprv9s21ZrQH143K48ig2wcAuZoEKaYdNRaShKFR3hLrgwsNW13QYRhXH6gAG1khxim6dw2RtAzF8RWbQxr1vvWUJFfEu2SJZhYbv6pfreMpuLB","xpub":"xpub661MyMwAqRbcGco98y9BGhjxscP7mtJJ4YB1r5kUFHQMNoNZ5y1mptze7J37JypkbrmBdnqTvSNzxL7cE1FrHg16qoj9S12MUpiYxVbTKQV"},"x2/":{"type":"bip32","xprv":null,"xpub":"xpub661MyMwAqRbcGrCDZaVs9VC7Z6579tsGvpqyDYZEHKg2MXoDkxhrWoukqvwDPXKdxVkYA6Hv9XHLETptfZfNpcJZmsUThdXXkTNGoBjQv1o"}}' + wallet_str = '{"addr_history":{"t3JnKqf852iC3NCZg9BewnWCh3qKFkMbU5z":[],"t3KFnk5wuukaq1XBB7zRHNHhJCEV1nkD1sW":[],"t3L81MYWF3f91wCGEUm8EYJCNcCM6fH9w9e":[],"t3LE1FCWbB1kMkwkTru3egfCbTQRsyGGdbd":[],"t3LoWiCSenAw8MDzz3LRLtgiJs8Hs9gN4Pq":[],"t3NmCNDRy9bZZg4RWp45gSVy2n9njsihcGD":[],"t3PsJHaJQ8MpyKvMXtdArfuKFu8Dvpk8197":[],"t3QjAuoHrceHR2AvbtHMFyjM72kxiqZBauo":[],"t3R2dKYwgQ6CvA4U6D7Q7zY74xgDpyRB6S9":[],"t3R9AHQgqvf7JgWr37BPDwfdwUskqhwwqSS":[],"t3T7VyBd5gshg8iFKhib8jttV39ENeSwiPq":[],"t3TStXAGfygmVaKyJRe9wEXBxtozA2abpqC":[],"t3W2p1V7RVhjmGCn6puQdsNbtBfs39BdaCa":[],"t3ZBfCX1pvEZQF4CqtjYy9Dtb3CtMFF1Kze":[],"t3Zsjy7B7VNJmBiQqr3eSx3oP3MEvn5U7H5":[],"t3baMb6LR9J3XWPLwZQPcpY5W6PjTd11fe6":[],"t3bkm5HRJHpqn1ib4B59YD26zN2pDo3C2id":[],"t3cHQszgPFCGPKctmf439fsHuGdFoNZvrhm":[],"t3cQF2KSUe4oday9acq7Tx6faropQiNYeJ2":[],"t3cSKEi9CTcR5aFy3QehzCcqQgXmSXumngf":[],"t3cX4Kcijit9c18opGD6YyMKTdmfcg62oaW":[],"t3cxBZfJMHmQggaSEHrcvWhcCzypACqvzK7":[],"t3d2zr5Fe2Wp3EnLGw9HpWPQKgiHbkebG6d":[],"t3fuuJjE7LahUUZ2rgeQugU2TyRGcXHUsPF":[],"t3gYydgxaYiDaNWoVuYwCMVRiiJM6hMmnNu":[],"t3gdiDuu8YY9ZZfpQ1woQwpy9QMgazTar4t":[]},"addresses":{"change":["t3JnKqf852iC3NCZg9BewnWCh3qKFkMbU5z","t3baMb6LR9J3XWPLwZQPcpY5W6PjTd11fe6","t3ZBfCX1pvEZQF4CqtjYy9Dtb3CtMFF1Kze","t3d2zr5Fe2Wp3EnLGw9HpWPQKgiHbkebG6d","t3LE1FCWbB1kMkwkTru3egfCbTQRsyGGdbd","t3T7VyBd5gshg8iFKhib8jttV39ENeSwiPq"],"receiving":["t3fuuJjE7LahUUZ2rgeQugU2TyRGcXHUsPF","t3L81MYWF3f91wCGEUm8EYJCNcCM6fH9w9e","t3W2p1V7RVhjmGCn6puQdsNbtBfs39BdaCa","t3TStXAGfygmVaKyJRe9wEXBxtozA2abpqC","t3Zsjy7B7VNJmBiQqr3eSx3oP3MEvn5U7H5","t3PsJHaJQ8MpyKvMXtdArfuKFu8Dvpk8197","t3QjAuoHrceHR2AvbtHMFyjM72kxiqZBauo","t3R2dKYwgQ6CvA4U6D7Q7zY74xgDpyRB6S9","t3R9AHQgqvf7JgWr37BPDwfdwUskqhwwqSS","t3LoWiCSenAw8MDzz3LRLtgiJs8Hs9gN4Pq","t3cxBZfJMHmQggaSEHrcvWhcCzypACqvzK7","t3cQF2KSUe4oday9acq7Tx6faropQiNYeJ2","t3cHQszgPFCGPKctmf439fsHuGdFoNZvrhm","t3cSKEi9CTcR5aFy3QehzCcqQgXmSXumngf","t3KFnk5wuukaq1XBB7zRHNHhJCEV1nkD1sW","t3cX4Kcijit9c18opGD6YyMKTdmfcg62oaW","t3bkm5HRJHpqn1ib4B59YD26zN2pDo3C2id","t3gYydgxaYiDaNWoVuYwCMVRiiJM6hMmnNu","t3gdiDuu8YY9ZZfpQ1woQwpy9QMgazTar4t","t3NmCNDRy9bZZg4RWp45gSVy2n9njsihcGD"]},"pruned_txo":{},"seed_version":13,"stored_height":485855,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"2of2","winpos-qt":[617,227,840,405],"x1/":{"seed":"speed cruise market wasp ability alarm hold essay grass coconut tissue recipe","type":"bip32","xprv":"xprv9s21ZrQH143K48ig2wcAuZoEKaYdNRaShKFR3hLrgwsNW13QYRhXH6gAG1khxim6dw2RtAzF8RWbQxr1vvWUJFfEu2SJZhYbv6pfreMpuLB","xpub":"xpub661MyMwAqRbcGco98y9BGhjxscP7mtJJ4YB1r5kUFHQMNoNZ5y1mptze7J37JypkbrmBdnqTvSNzxL7cE1FrHg16qoj9S12MUpiYxVbTKQV"},"x2/":{"type":"bip32","xprv":null,"xpub":"xpub661MyMwAqRbcGrCDZaVs9VC7Z6579tsGvpqyDYZEHKg2MXoDkxhrWoukqvwDPXKdxVkYA6Hv9XHLETptfZfNpcJZmsUThdXXkTNGoBjQv1o"}}' self._upgrade_storage(wallet_str) ########## diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py index 89c8bfa6..65cfa3f2 100644 --- a/lib/tests/test_transaction.py +++ b/lib/tests/test_transaction.py @@ -1,10 +1,9 @@ import unittest + from lib import transaction from lib.bitcoin import TYPE_ADDRESS - from lib.keystore import xpubkey_to_address - -from lib.util import bh2u +from lib.util import bh2u, bfh unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' @@ -133,6 +132,11 @@ class TestTransaction(unittest.TestCase): self.assertEqual(tx.estimated_weight(), 772) self.assertEqual(tx.estimated_size(), 193) + def test_estimated_output_size(self): + estimated_output_size = transaction.Transaction.estimated_output_size + self.assertEqual(estimated_output_size('t1MZDS9LxiXasLqR5fMDK4kDa8TJjSFsMsq'), 34) + self.assertEqual(estimated_output_size('t3NSSQe2KNgLcTWy2WsiRAkr7NTtZ15fhLn'), 32) + def test_errors(self): with self.assertRaises(TypeError): transaction.Transaction.pay_script(output_type=None, addr='') @@ -148,29 +152,67 @@ class TestTransaction(unittest.TestCase): tx = transaction.Transaction(v2_blob) self.assertEqual(tx.txid(), "b97f9180173ab141b61b9f944d841e60feec691d6daab4d4d932b24dd36606fe") + def test_get_address_from_output_script(self): + # the inverse of this test is in test_bitcoin: test_address_to_script + addr_from_script = lambda script: transaction.get_address_from_output_script(bfh(script)) + ADDR = transaction.TYPE_ADDRESS + + # base58 p2pkh + self.assertEqual((ADDR, 't1MZDS9LxiXasLqR5fMDK4kDa8TJjSFsMsq'), addr_from_script('76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')) + self.assertEqual((ADDR, 't1U7SgL7CWNnawSvZD8k8JgwWUygasy2cp1'), addr_from_script('76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')) + + # base58 p2sh + self.assertEqual((ADDR, 't3NSSQe2KNgLcTWy2WsiRAkr7NTtZ15fhLn'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')) + self.assertEqual((ADDR, 't3grLzdTrjSSiCFXzxV5YCvkYZt2tJjDLau'), addr_from_script('a914f47c8954e421031ad04ecd8e7752c9479206b9d387')) + +##### + + def _run_naive_tests_on_tx(self, raw_tx, txid): + tx = transaction.Transaction(raw_tx) + self.assertEqual(txid, tx.txid()) + self.assertEqual(raw_tx, tx.serialize()) + self.assertTrue(tx.estimated_size() >= 0) + def test_txid_coinbase_to_p2pk(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000') - self.assertEqual('dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000' + txid = 'dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_coinbase_to_p2pkh(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000') - self.assertEqual('4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000' + txid = '4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2pkh(self): - tx = transaction.Transaction('010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000') - self.assertEqual('90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9', tx.txid()) + raw_tx = '010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000' + txid = '90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2sh(self): - tx = transaction.Transaction('0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000') - self.assertEqual('172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5', tx.txid()) + raw_tx = '0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000' + txid = '172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2pkh(self): - tx = transaction.Transaction('0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000') - self.assertEqual('24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce', tx.txid()) + raw_tx = '0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000' + txid = '24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2sh(self): - tx = transaction.Transaction('010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000') - self.assertEqual('155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc', tx.txid()) + raw_tx = '010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000' + txid = '155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc' + self._run_naive_tests_on_tx(raw_tx, txid) + + # input: p2sh, not multisig + def test_txid_regression_issue_3899(self): + raw_tx = '0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000' + txid = 'f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d' + self._run_naive_tests_on_tx(raw_tx, txid) + + def test_txid_negative_version_num(self): + raw_tx = 'f0b47b9a01ecf5e5c3bbf2cf1f71ecdc7f708b0b222432e914b394e24aad1494a42990ddfc000000008b483045022100852744642305a99ad74354e9495bf43a1f96ded470c256cd32e129290f1fa191022030c11d294af6a61b3da6ed2c0c296251d21d113cfd71ec11126517034b0dcb70014104a0fe6e4a600f859a0932f701d3af8e0ecd4be886d91045f06a5a6b931b95873aea1df61da281ba29cadb560dad4fc047cf47b4f7f2570da4c0b810b3dfa7e500ffffffff0240420f00000000001976a9147eeacb8a9265cd68c92806611f704fc55a21e1f588ac05f00d00000000001976a914eb3bd8ccd3ba6f1570f844b59ba3e0a667024a6a88acff7f0000' + txid = 'c659729a7fea5071361c2c1a68551ca2bf77679b27086cc415adeeb03852e369' + self._run_naive_tests_on_tx(raw_tx, txid) class NetworkMock(object): diff --git a/lib/tests/test_wallet_vertical.py b/lib/tests/test_wallet_vertical.py index cc3c4798..a2016931 100644 --- a/lib/tests/test_wallet_vertical.py +++ b/lib/tests/test_wallet_vertical.py @@ -5,44 +5,59 @@ import lib.bitcoin as bitcoin import lib.keystore as keystore import lib.storage as storage import lib.wallet as wallet +from lib import constants + +from . import TestCaseForTestnet -# TODO: 2fa -class TestWalletKeystoreAddressIntegrity(unittest.TestCase): +class WalletIntegrityHelper: gap_limit = 1 # make tests run faster - def _check_seeded_keystore_sanity(self, ks): - self.assertTrue (ks.is_deterministic()) - self.assertFalse(ks.is_watching_only()) - self.assertFalse(ks.can_import()) - self.assertTrue (ks.has_seed()) + @classmethod + def check_seeded_keystore_sanity(cls, test_obj, ks): + test_obj.assertTrue(ks.is_deterministic()) + test_obj.assertFalse(ks.is_watching_only()) + test_obj.assertFalse(ks.can_import()) + test_obj.assertTrue(ks.has_seed()) - def _check_xpub_keystore_sanity(self, ks): - self.assertTrue (ks.is_deterministic()) - self.assertTrue (ks.is_watching_only()) - self.assertFalse(ks.can_import()) - self.assertFalse(ks.has_seed()) + @classmethod + def check_xpub_keystore_sanity(cls, test_obj, ks): + test_obj.assertTrue(ks.is_deterministic()) + test_obj.assertTrue(ks.is_watching_only()) + test_obj.assertFalse(ks.can_import()) + test_obj.assertFalse(ks.has_seed()) - def _create_standard_wallet(self, ks): + @classmethod + def create_standard_wallet(cls, ks): store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') store.put('keystore', ks.dump()) - store.put('gap_limit', self.gap_limit) + store.put('gap_limit', cls.gap_limit) w = wallet.Standard_Wallet(store) w.synchronize() return w - def _create_multisig_wallet(self, ks1, ks2): + @classmethod + def create_multisig_wallet(cls, ks1, ks2, ks3=None): + """Creates a 2-of-2 or 2-of-3 multisig wallet.""" store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') - multisig_type = "%dof%d" % (2, 2) - store.put('wallet_type', multisig_type) store.put('x%d/' % 1, ks1.dump()) store.put('x%d/' % 2, ks2.dump()) - store.put('gap_limit', self.gap_limit) + if ks3 is None: + multisig_type = "%dof%d" % (2, 2) + else: + multisig_type = "%dof%d" % (2, 3) + store.put('x%d/' % 3, ks3.dump()) + store.put('wallet_type', multisig_type) + store.put('gap_limit', cls.gap_limit) w = wallet.Multisig_Wallet(store) w.synchronize() return w + +# TODO passphrase/seed_extension +class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase): + @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_seed_standard(self, mock_write): seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' @@ -50,12 +65,14 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) + self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6') self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) + self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], 't1fFMuEC9XFGsEUEPzpEE8jhxpcEMs369xJ') self.assertEqual(w.get_change_addresses()[0], 't1cKFzsmq8d97RtePBbqS1WebLofuXaXkzF') @@ -67,12 +84,13 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) self.assertTrue(isinstance(ks, keystore.Old_KeyStore)) self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) + self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], 't1YAqEWYrfi9CbW5LgmayAvjDE5T5MgaYiD') self.assertEqual(w.get_change_addresses()[0], 't1cJ799hEFa5AHmB3ReeDo3Rr2X4quderf4') @@ -86,9 +104,11 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) + self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD') self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) + self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], 't1PbiEBABXU1E4GEnE31cUPULDoYYWVMpjs') self.assertEqual(w.get_change_addresses()[0], 't1Z8gbq4eeVbg89ACGddq1T6yfEP9bQx9Ki') @@ -99,15 +119,18 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(bitcoin.seed_type(seed_words), 'standard') ks1 = keystore.from_seed(seed_words, '', True) - self._check_seeded_keystore_sanity(ks1) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1) self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore)) + self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6') self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c') + # electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud ks2 = keystore.from_xpub('xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDbenT33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) + self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], 't3KcK3kAJerAahSJhN6Pt729u7ficQqK4XX') self.assertEqual(w.get_change_addresses()[0], 't3PQ7wZhzpoywPLnD1nfcd5sPYLtw2qh5ak') @@ -119,13 +142,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks1 = keystore.from_bip39_seed(seed_words, '', "m/45'/0") self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore)) + self.assertEqual(ks1.xprv, 'xprv9vyEFyXf7pYVv4eDU3hhuCEAHPHNGuxX73nwtYdpbLcqwJCPwFKknAK8pHWuHHBirCzAPDZ7UJHrYdhLfn1NkGp9rk3rVz2aEqrT93qKRD9') self.assertEqual(ks1.xpub, 'xpub69xafV4YxC6o8Yiga5EiGLAtqR7rgNgNUGiYgw3S9g9pp6XYUne1KxdcfYtxwmA3eBrzMFuYcNQKfqsXCygCo4GxQFHfywxpUbKNfYvGJka') + # bip39 seed: tray machine cook badge night page project uncover ritual toward person enact + # der: m/45'/0 ks2 = keystore.from_xpub('xpub6Bco9vrgo8rNUSi8Bjomn8xLA41DwPXeuPcgJamNRhTTyGVHsp8fZXaGzp9ypHoei16J6X3pumMAP1u3Dy4jTSWjm4GZowL7Dcn9u4uZC9W') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) + self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], 't3ZvKyVcMRf5rofUFgN8gk23jtCkRh12Lja') self.assertEqual(w.get_change_addresses()[0], 't3JaafdGtfhXJzCrupYct6tJBdD2XZUtm8H') diff --git a/lib/transaction.py b/lib/transaction.py index 1859b9a0..f0e5b675 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -32,6 +32,8 @@ from .util import print_error, profiler from . import bitcoin from .bitcoin import * import struct +import traceback +import sys # # Workalike python implementation of Bitcoin's CDataStream class. @@ -227,10 +229,10 @@ opcodes = Enumeration("Opcodes", [ "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", "OP_CHECKMULTISIGVERIFY", - ("OP_SINGLEBYTE_END", 0xF0), - ("OP_DOUBLEBYTE_BEGIN", 0xF000), - "OP_PUBKEY", "OP_PUBKEYHASH", - ("OP_INVALIDOPCODE", 0xFFFF), + ("OP_NOP1", 0xB0), + ("OP_CHECKLOCKTIMEVERIFY", 0xB1), ("OP_CHECKSEQUENCEVERIFY", 0xB2), + "OP_NOP4", "OP_NOP5", "OP_NOP6", "OP_NOP7", "OP_NOP8", "OP_NOP9", "OP_NOP10", + ("OP_INVALIDOPCODE", 0xFF), ]) @@ -240,10 +242,6 @@ def script_GetOp(_bytes): vch = None opcode = _bytes[i] i += 1 - if opcode >= opcodes.OP_SINGLEBYTE_END: - opcode <<= 8 - opcode |= _bytes[i] - i += 1 if opcode <= opcodes.OP_PUSHDATA4: nSize = opcode @@ -303,7 +301,8 @@ def parse_scriptSig(d, _bytes): decoded = [ x for x in script_GetOp(_bytes) ] except Exception as e: # coinbase transactions raise an exception - print_error("cannot find address in input script", bh2u(_bytes)) + print_error("parse_scriptSig: cannot find address in input script (coinbase?)", + bh2u(_bytes)) return match = [ opcodes.OP_PUSHDATA4 ] @@ -320,9 +319,9 @@ def parse_scriptSig(d, _bytes): d['pubkeys'] = ["(pubkey)"] return - # non-generated TxIn transactions push a signature - # (seventy-something bytes) and then their public key - # (65 bytes) onto the stack: + # p2pkh TxIn transactions push a signature + # (71-73 bytes) and then their public key + # (33 or 65 bytes) onto the stack: match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): sig = bh2u(decoded[0][1]) @@ -331,7 +330,8 @@ def parse_scriptSig(d, _bytes): signatures = parse_sig([sig]) pubkey, address = xpubkey_to_address(x_pubkey) except: - print_error("cannot find address in input script", bh2u(_bytes)) + print_error("parse_scriptSig: cannot find address in input script (p2pkh?)", + bh2u(_bytes)) return d['type'] = 'p2pkh' d['signatures'] = signatures @@ -343,37 +343,49 @@ def parse_scriptSig(d, _bytes): # p2sh transaction, m of n match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1) - if not match_decoded(decoded, match): - print_error("cannot find address in input script", bh2u(_bytes)) + if match_decoded(decoded, match): + x_sig = [bh2u(x[1]) for x in decoded[1:-1]] + try: + m, n, x_pubkeys, pubkeys, redeemScript = parse_redeemScript(decoded[-1][1]) + except NotRecognizedRedeemScript: + print_error("parse_scriptSig: cannot find address in input script (p2sh?)", + bh2u(_bytes)) + # we could still guess: + # d['address'] = hash160_to_p2sh(hash_160(decoded[-1][1])) + return + # write result in d + d['type'] = 'p2sh' + d['num_sig'] = m + d['signatures'] = parse_sig(x_sig) + d['x_pubkeys'] = x_pubkeys + d['pubkeys'] = pubkeys + d['redeemScript'] = redeemScript + d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript))) return - x_sig = [bh2u(x[1]) for x in decoded[1:-1]] - m, n, x_pubkeys, pubkeys, redeemScript = parse_redeemScript(decoded[-1][1]) - # write result in d - d['type'] = 'p2sh' - d['num_sig'] = m - d['signatures'] = parse_sig(x_sig) - d['x_pubkeys'] = x_pubkeys - d['pubkeys'] = pubkeys - d['redeemScript'] = redeemScript - d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript))) + + print_error("parse_scriptSig: cannot find address in input script (unknown)", + bh2u(_bytes)) def parse_redeemScript(s): dec2 = [ x for x in script_GetOp(s) ] - m = dec2[0][0] - opcodes.OP_1 + 1 - n = dec2[-2][0] - opcodes.OP_1 + 1 + try: + m = dec2[0][0] - opcodes.OP_1 + 1 + n = dec2[-2][0] - opcodes.OP_1 + 1 + except IndexError: + raise NotRecognizedRedeemScript() op_m = opcodes.OP_1 + m - 1 op_n = opcodes.OP_1 + n - 1 match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ] if not match_decoded(dec2, match_multisig): - print_error("cannot find address in input script", bh2u(s)) raise NotRecognizedRedeemScript() x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]] pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys] redeemScript = multisig_script(pubkeys, m) return m, n, x_pubkeys, pubkeys, redeemScript -def get_address_from_output_script(_bytes): + +def get_address_from_output_script(_bytes, *, net=None): decoded = [x for x in script_GetOp(_bytes)] # The Genesis Block, self-payments, and pay-by-IP-address payments look like: @@ -386,12 +398,12 @@ def get_address_from_output_script(_bytes): # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1]) + return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1], net=net) # p2sh match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1]) + return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net) return TYPE_SCRIPT, bh2u(_bytes) @@ -405,19 +417,23 @@ def parse_input(vds): d['prevout_hash'] = prevout_hash d['prevout_n'] = prevout_n d['sequence'] = sequence + d['x_pubkeys'] = [] + d['pubkeys'] = [] + d['signatures'] = {} + d['address'] = None + d['num_sig'] = 0 if prevout_hash == '00'*32: d['type'] = 'coinbase' d['scriptSig'] = bh2u(scriptSig) else: - d['x_pubkeys'] = [] - d['pubkeys'] = [] - d['signatures'] = {} - d['address'] = None d['type'] = 'unknown' - d['num_sig'] = 0 if scriptSig: d['scriptSig'] = bh2u(scriptSig) - parse_scriptSig(d, scriptSig) + try: + parse_scriptSig(d, scriptSig) + except BaseException: + traceback.print_exc(file=sys.stderr) + print_error('failed to parse scriptSig', bh2u(scriptSig)) else: d['scriptSig'] = '' @@ -479,7 +495,7 @@ class Transaction: elif isinstance(raw, dict): self.raw = raw['hex'] else: - raise BaseException("cannot initialize transaction", raw) + raise Exception("cannot initialize transaction", raw) self._inputs = None self._outputs = None self.locktime = 0 @@ -503,6 +519,8 @@ class Transaction: @classmethod def get_sorted_pubkeys(self, txin): # sort pubkeys and x_pubkeys, using the order of pubkeys + if txin['type'] == 'coinbase': + return [], [] x_pubkeys = txin['x_pubkeys'] pubkeys = txin.get('pubkeys') if pubkeys is None: @@ -603,6 +621,8 @@ class Transaction: def get_siglist(self, txin, estimate_size=False): # if we have enough signatures, we use the actual pubkeys # otherwise, use extended pubkeys (with bip32 derivation) + if txin['type'] == 'coinbase': + return [], [] num_sig = txin.get('num_sig', 1) if estimate_size: pubkey_size = self.estimate_pubkey_size_for_txin(txin) @@ -645,7 +665,9 @@ class Transaction: return script @classmethod - def is_txin_complete(self, txin): + def is_txin_complete(cls, txin): + if txin['type'] == 'coinbase': + return True num_sig = txin.get('num_sig', 1) x_signatures = txin['signatures'] signatures = list(filter(None, x_signatures)) @@ -653,13 +675,13 @@ class Transaction: @classmethod def get_preimage_script(self, txin): + pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) if txin['type'] == 'p2pkh': return bitcoin.address_to_script(txin['address']) elif txin['type'] in ['p2sh']: - pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) return multisig_script(pubkeys, txin['num_sig']) elif txin['type'] == 'p2pk': - pubkey = txin['pubkeys'][0] + pubkey = pubkeys[0] return bitcoin.public_key_to_p2pk_script(pubkey) else: raise TypeError('Unknown txin type', txin['type']) @@ -668,6 +690,14 @@ class Transaction: def serialize_outpoint(self, txin): return bh2u(bfh(txin['prevout_hash'])[::-1]) + int_to_hex(txin['prevout_n'], 4) + @classmethod + def get_outpoint_from_txin(cls, txin): + if txin['type'] == 'coinbase': + return None + prevout_hash = txin['prevout_hash'] + prevout_n = txin['prevout_n'] + return prevout_hash + ':%d' % prevout_n + @classmethod def serialize_input(self, txin, script): # Prev hash and index @@ -761,6 +791,13 @@ class Transaction: input_size = len(cls.serialize_input(txin, script)) // 2 return 4 * input_size + @classmethod + def estimated_output_size(cls, address): + """Return an estimate of serialized output size in bytes.""" + script = bitcoin.address_to_script(address) + # 8 byte value + 1 byte script len + script + return 9 + len(script) // 2 + @classmethod def virtual_size_from_weight(cls, weight): return weight // 4 + (weight % 4 > 0) @@ -814,7 +851,8 @@ class Transaction: private_key = bitcoin.MySigningKey.from_secret_exponent(secexp, curve = SECP256k1) public_key = private_key.get_verifying_key() sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der_canonize) - assert public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der) + if not public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der_canonize): + raise Exception('Sanity check verifying our own signature failed.') txin['signatures'][j] = bh2u(sig) + '01' #txin['x_pubkeys'][j] = pubkey txin['pubkeys'][j] = pubkey # needed for fd keys diff --git a/lib/util.py b/lib/util.py index 5176d748..01e90d57 100644 --- a/lib/util.py +++ b/lib/util.py @@ -41,28 +41,102 @@ def inv_dict(d): base_units = {'ZEC':8, 'mZEC':5, 'uZEC':2} -fee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')] def normalize_version(v): return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] class NotEnoughFunds(Exception): pass + +class NoDynamicFeeEstimates(Exception): + def __str__(self): + return _('Dynamic fee estimates not available') + + class InvalidPassword(Exception): def __str__(self): return _("Incorrect password") + +class FileImportFailed(Exception): + def __init__(self, message=''): + self.message = str(message) + + def __str__(self): + return _("Failed to import from file.") + "\n" + self.message + + +class FileExportFailed(Exception): + def __init__(self, message=''): + self.message = str(message) + + def __str__(self): + return _("Failed to export to file.") + "\n" + self.message + + +class TimeoutException(Exception): + def __init__(self, message=''): + self.message = str(message) + + def __str__(self): + if not self.message: + return _("Operation timed out.") + 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): '''An exception that is suppressed from the user''' pass +class Satoshis(object): + def __new__(cls, value): + self = super(Satoshis, cls).__new__(cls) + self.value = value + return self + + def __repr__(self): + return 'Satoshis(%d)'%self.value + + def __str__(self): + return format_satoshis(self.value) + " BTC" + +class Fiat(object): + def __new__(cls, value, ccy): + self = super(Fiat, cls).__new__(cls) + self.ccy = ccy + self.value = value + return self + + def __repr__(self): + return 'Fiat(%s)'% self.__str__() + + def __str__(self): + if self.value.is_nan(): + return _('No Data') + else: + return "{:.2f}".format(self.value) + ' ' + self.ccy + class MyEncoder(json.JSONEncoder): def default(self, obj): from .transaction import Transaction if isinstance(obj, Transaction): return obj.as_dict() + if isinstance(obj, Satoshis): + return str(obj) + if isinstance(obj, Fiat): + return str(obj) + if isinstance(obj, Decimal): + return str(obj) + if isinstance(obj, datetime): + return obj.isoformat(' ')[:-3] return super(MyEncoder, self).default(obj) class PrintError(object): @@ -71,8 +145,12 @@ class PrintError(object): return self.__class__.__name__ def print_error(self, *msg): + # only prints with --verbose flag print_error("[%s]" % self.diagnostic_name(), *msg) + def print_stderr(self, *msg): + print_stderr("[%s]" % self.diagnostic_name(), *msg) + def print_msg(self, *msg): print_msg("[%s]" % self.diagnostic_name(), *msg) @@ -247,8 +325,8 @@ def android_check_data_dir(): old_electrum_dir = ext_dir + '/electrum-zcash' if not os.path.exists(data_dir) and os.path.exists(old_electrum_dir): import shutil - new_headers_path = android_headers_dir() + android_headers_file_name() - old_headers_path = old_electrum_dir + android_headers_file_name() + new_headers_path = android_headers_dir() + '/blockchain_headers' + old_headers_path = old_electrum_dir + '/blockchain_headers' if not os.path.exists(new_headers_path) and os.path.exists(old_headers_path): print_error("Moving headers file to", new_headers_path) shutil.move(old_headers_path, new_headers_path) @@ -348,7 +426,7 @@ def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespa return 'unknown' x = int(x) # Some callers pass Decimal scale_factor = pow (10, decimal_point) - integer_part = "{:n}".format(int(abs(x) / scale_factor)) + integer_part = "{:d}".format(int(abs(x) / scale_factor)) if x < 0: integer_part = '-' + integer_part elif is_diff: @@ -365,10 +443,9 @@ def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespa return result def timestamp_to_datetime(timestamp): - try: - return datetime.fromtimestamp(timestamp) - except: + if timestamp is None: return None + return datetime.fromtimestamp(timestamp) def format_time(timestamp): date = timestamp_to_datetime(timestamp) @@ -429,22 +506,22 @@ def time_difference(distance_in_time, include_seconds): return "over %d years" % (round(distance_in_minutes / 525600)) mainnet_block_explorers = { - 'blockexplorer.com': ('https://zcash.blockexplorer.com/blocks', - {'tx': 'transactions', 'addr': 'addresses'}), - 'system default': ('blockchain:', - {'tx': 'tx', 'addr': 'address'}), + 'blockexplorer.com': ('https://zcash.blockexplorer.com/blocks/', + {'tx': 'transactions/', 'addr': 'addresses/'}), + 'system default': ('blockchain:/', + {'tx': 'tx/', 'addr': 'address/'}), } testnet_block_explorers = { 'testnet.z.cash': ('https://explorer.testnet.z.cash/', - {'tx': 'tx', 'addr': 'address'}), - 'system default': ('blockchain:', - {'tx': 'tx', 'addr': 'address'}), + {'tx': 'tx/', 'addr': 'address/'}), + 'system default': ('blockchain:/', + {'tx': 'tx/', 'addr': 'address/'}), } def block_explorer_info(): - from . import bitcoin - return testnet_block_explorers if bitcoin.NetworkConstants.TESTNET else mainnet_block_explorers + from . import constants + return testnet_block_explorers if constants.net.TESTNET else mainnet_block_explorers def block_explorer(config): return config.get('block_explorer', 'blockexplorer.com') @@ -460,7 +537,7 @@ def block_explorer_URL(config, kind, item): if not kind_str: return url_parts = [be_tuple[0], kind_str, item] - return "/".join(url_parts) + return ''.join(url_parts) # URL decode #_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE) @@ -472,12 +549,12 @@ def parse_URI(uri, on_pr=None): if ':' not in uri: if not bitcoin.is_address(uri): - raise BaseException("Not a Zcash address") + raise Exception("Not a Zcash address") return {'address': uri} u = urllib.parse.urlparse(uri) if u.scheme != 'zcash': - raise BaseException("Not a Zcash URI") + raise Exception("Not a Zcash URI") address = u.path # python for android fails to parse query @@ -494,7 +571,7 @@ def parse_URI(uri, on_pr=None): out = {k: v[0] for k, v in pq.items()} if address: if not bitcoin.is_address(address): - raise BaseException("Invalid Zcash address:" + address) + raise Exception("Invalid Zcash address:" + address) out['address'] = address if 'amount' in out: am = out['amount'] @@ -643,10 +720,6 @@ class SocketPipe: print_error("SSLError:", e) time.sleep(0.1) continue - except OSError as e: - print_error("OSError", e) - time.sleep(0.1) - continue class QueuePipe: @@ -683,25 +756,56 @@ class QueuePipe: self.send(request) -def check_www_dir(rdir): - import urllib, shutil, os - if not os.path.exists(rdir): - os.mkdir(rdir) - index = os.path.join(rdir, 'index.html') - if not os.path.exists(index): - print_error("copying index.html") - src = os.path.join(os.path.dirname(__file__), 'www', 'index.html') - shutil.copy(src, index) - files = [ - "https://code.jquery.com/jquery-1.9.1.min.js", - "https://raw.githubusercontent.com/davidshimjs/qrcodejs/master/qrcode.js", - "https://code.jquery.com/ui/1.10.3/jquery-ui.js", - "https://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" - ] - for URL in files: - path = urllib.parse.urlsplit(URL).path - filename = os.path.basename(path) - path = os.path.join(rdir, filename) - if not os.path.exists(path): - print_error("downloading ", URL) - urllib.request.urlretrieve(URL, path) + + +def setup_thread_excepthook(): + """ + Workaround for `sys.excepthook` thread bug from: + http://bugs.python.org/issue1230540 + + Call once from the main thread before creating any threads. + """ + + init_original = threading.Thread.__init__ + + def init(self, *args, **kwargs): + + init_original(self, *args, **kwargs) + run_original = self.run + + def run_with_except_hook(*args2, **kwargs2): + try: + run_original(*args2, **kwargs2) + except Exception: + sys.excepthook(*sys.exc_info()) + + self.run = run_with_except_hook + + threading.Thread.__init__ = init + + +def versiontuple(v): + return tuple(map(int, (v.split(".")))) + + +def import_meta(path, validater, load_meta): + try: + with open(path, 'r', encoding='utf-8') as f: + d = validater(json.loads(f.read())) + load_meta(d) + #backwards compatibility for JSONDecodeError + except ValueError: + traceback.print_exc(file=sys.stderr) + raise FileImportFailed(_("Invalid JSON code.")) + except BaseException as e: + traceback.print_exc(file=sys.stdout) + raise FileImportFailed(e) + + +def export_meta(meta, fileName): + try: + with open(fileName, 'w+', encoding='utf-8') as f: + json.dump(meta, f, indent=4, sort_keys=True) + except (IOError, os.error) as e: + traceback.print_exc(file=sys.stderr) + raise FileExportFailed(e) diff --git a/lib/verifier.py b/lib/verifier.py index c1fc9a82..236ffbfe 100644 --- a/lib/verifier.py +++ b/lib/verifier.py @@ -36,22 +36,37 @@ class SPV(ThreadJob): self.merkle_roots = {} def run(self): + interface = self.network.interface + if not interface: + return + blockchain = interface.blockchain + if not blockchain: + return lh = self.network.get_local_height() unverified = self.wallet.get_unverified_txs() for tx_hash, tx_height in unverified.items(): # do not request merkle branch before headers are available - if (tx_height > 0) and (tx_hash not in self.merkle_roots) and (tx_height <= lh): - request = ('blockchain.transaction.get_merkle', - [tx_hash, tx_height]) - self.network.send([request], self.verify_merkle) - self.print_error('requested merkle', tx_hash) - self.merkle_roots[tx_hash] = None + if (tx_height > 0) and (tx_height <= lh): + header = blockchain.read_header(tx_height) + if header is None: + index = tx_height // 2016 + if index < len(blockchain.checkpoints): + self.network.request_chunk(interface, index) + else: + if tx_hash not in self.merkle_roots: + request = ('blockchain.transaction.get_merkle', + [tx_hash, tx_height]) + self.network.send([request], self.verify_merkle) + self.print_error('requested merkle', tx_hash) + self.merkle_roots[tx_hash] = None if self.network.blockchain() != self.blockchain: self.blockchain = self.network.blockchain() self.undo_verifications() def verify_merkle(self, r): + if self.wallet.verifier is None: + return # we have been killed, this was just an orphan callback if r.get('error'): self.print_error('received an error:', r) return @@ -64,17 +79,26 @@ class SPV(ThreadJob): pos = merkle.get('pos') merkle_root = self.hash_merkle_root(merkle['merkle'], tx_hash, pos) header = self.network.blockchain().read_header(tx_height) - if not header or header.get('merkle_root') != merkle_root: - # FIXME: we should make a fresh connection to a server to - # recover from this, as this TX will now never verify - self.print_error("merkle verification failed for", tx_hash) + # FIXME: if verification fails below, + # we should make a fresh connection to a server to + # recover from this, as this TX will now never verify + if not header: + self.print_error( + "merkle verification failed for {} (missing header {})" + .format(tx_hash, tx_height)) + return + if header.get('merkle_root') != merkle_root: + self.print_error( + "merkle verification failed for {} (merkle root mismatch {} != {})" + .format(tx_hash, header.get('merkle_root'), merkle_root)) return # we passed all the tests self.merkle_roots[tx_hash] = merkle_root self.print_error("verified %s" % tx_hash) self.wallet.add_verified_tx(tx_hash, (tx_height, header.get('timestamp'), pos)) - def hash_merkle_root(self, merkle_s, target_hash, pos): + @classmethod + def hash_merkle_root(cls, merkle_s, target_hash, pos): h = hash_decode(target_hash) for i in range(len(merkle_s)): item = merkle_s[i] diff --git a/lib/version.py b/lib/version.py index 22864a33..eb68f918 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,4 +1,4 @@ -ELECTRUM_VERSION = '3.0.6' # version of the client package +ELECTRUM_VERSION = '3.1.3' # version of the client package PROTOCOL_VERSION = '1.2' # protocol version requested # The hash of the mnemonic seed must begin with this diff --git a/lib/wallet.py b/lib/wallet.py index 4f2452f0..e02a0d43 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -34,16 +34,24 @@ import time import json import copy import errno +import traceback from functools import partial from collections import defaultdict +from numbers import Number +from decimal import Decimal +import itertools + +import sys from .i18n import _ -from .util import NotEnoughFunds, PrintError, UserCancelled, profiler, format_satoshis +from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, + format_satoshis, NoDynamicFeeEstimates, TimeoutException, + WalletFileException, BitcoinException) from .bitcoin import * from .version import * from .keystore import load_keystore, Hardware_KeyStore -from .storage import multisig_type +from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW from . import transaction from .transaction import Transaction @@ -59,19 +67,21 @@ from .paymentrequest import InvoiceStore from .contacts import Contacts TX_STATUS = [ - _('Replaceable'), - _('Unconfirmed parent'), - _('Low fee'), _('Unconfirmed'), + _('Unconfirmed parent'), _('Not Verified'), + _('Local'), ] +TX_HEIGHT_LOCAL = -2 +TX_HEIGHT_UNCONF_PARENT = -1 +TX_HEIGHT_UNCONFIRMED = 0 def relayfee(network): - RELAY_FEE = 1000 + from .simple_config import FEERATE_DEFAULT_RELAY MAX_RELAY_FEE = 10000 - f = network.relay_fee if network and network.relay_fee else RELAY_FEE + f = network.relay_fee if network and network.relay_fee else FEERATE_DEFAULT_RELAY return min(f, MAX_RELAY_FEE) def dust_threshold(network): @@ -122,7 +132,8 @@ def sweep_preparations(privkeys, network, imax=100): # we also search for pay-to-pubkey outputs find_utxos_for_privkey('p2pk', privkey, compressed) if not inputs: - raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)')) + raise Exception(_('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 @@ -134,18 +145,33 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100): tx = Transaction.from_io(inputs, outputs) fee = config.estimate_fee(tx.estimated_size()) if total - fee < 0: - raise BaseException(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee)) + raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee)) if total - fee < dust_threshold(network): - raise BaseException(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network))) + raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network))) outputs = [(TYPE_ADDRESS, recipient, total - fee)] locktime = network.get_local_height() tx = Transaction.from_io(inputs, outputs, locktime=locktime) + tx.BIP_LI01_sort() tx.sign(keypairs) return tx +class AddTransactionException(Exception): + pass + + +class UnrelatedTransactionException(AddTransactionException): + def __str__(self): + return _("Transaction is unrelated to this wallet.") + + +class NotIsMineTransactionException(AddTransactionException): + def __str__(self): + return _("Only transactions with inputs owned by the wallet can be added.") + + class Abstract_Wallet(PrintError): """ Wallet classes are created to handle various address generation methods. @@ -162,37 +188,42 @@ class Abstract_Wallet(PrintError): self.synchronizer = None self.verifier = None - self.gap_limit_for_change = 6 # constant + self.gap_limit_for_change = 6 # constant + + # locks: if you need to take multiple ones, acquire them in the order they are defined here! + self.lock = threading.RLock() + self.transaction_lock = threading.RLock() + # saved fields self.use_change = storage.get('use_change', True) self.multiple_change = storage.get('multiple_change', False) self.labels = storage.get('labels', {}) self.frozen_addresses = set(storage.get('frozen_addresses',[])) self.history = storage.get('addr_history',{}) # address -> list(txid, height) + self.fiat_value = storage.get('fiat_value', {}) + self.receive_requests = storage.get('payment_requests', {}) - self.load_keystore() - self.load_addresses() - self.load_transactions() - self.build_reverse_history() - - # load requests - self.receive_requests = self.storage.get('payment_requests', {}) + # Verified transactions. Each value is a (height, timestamp, block_pos) tuple. Access with self.lock. + self.verified_tx = storage.get('verified_tx3', {}) # Transactions pending verification. A map from tx hash to transaction # height. Access is not contended so no lock is needed. self.unverified_tx = defaultdict(int) - # Verified transactions. Each value is a (height, timestamp, block_pos) tuple. Access with self.lock. - self.verified_tx = storage.get('verified_tx3', {}) + self.load_keystore() + self.load_addresses() + self.test_addresses_sanity() + self.load_transactions() + self.check_history() + self.load_unverified_transactions() + self.load_local_history() + self.build_spent_outpoints() + self.remove_local_transactions_we_dont_have() # there is a difference between wallet.up_to_date and interface.is_up_to_date() # interface.is_up_to_date() returns true when all requests have been answered and processed # wallet.up_to_date is true when the wallet is synchronized (stronger requirement) self.up_to_date = False - self.lock = threading.Lock() - self.transaction_lock = threading.Lock() - - self.check_history() # save wallet type the first time if self.storage.get('wallet_type') is None: @@ -202,6 +233,8 @@ class Abstract_Wallet(PrintError): self.invoices = InvoiceStore(self.storage) self.contacts = Contacts(self.storage) + self.coin_price_cache = {} + def diagnostic_name(self): return self.basename() @@ -223,10 +256,24 @@ class Abstract_Wallet(PrintError): for tx_hash, raw in tx_list.items(): tx = Transaction(raw) self.transactions[tx_hash] = tx - if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None and (tx_hash not in self.pruned_txo.values()): + if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None \ + and (tx_hash not in self.pruned_txo.values()): self.print_error("removing unreferenced tx", tx_hash) self.transactions.pop(tx_hash) + @profiler + def load_local_history(self): + self._history_local = {} # address -> set(txid) + for txid in itertools.chain(self.txi, self.txo): + self._add_tx_to_local_history(txid) + + def remove_local_transactions_we_dont_have(self): + txid_set = set(self.txi) | set(self.txo) + for txid in txid_set: + tx_height = self.get_tx_height(txid)[0] + if tx_height == TX_HEIGHT_LOCAL and txid not in self.transactions: + self.remove_transaction(txid) + @profiler def save_transactions(self, write=False): with self.transaction_lock: @@ -243,32 +290,38 @@ class Abstract_Wallet(PrintError): self.storage.write() def clear_history(self): - with self.transaction_lock: - self.txi = {} - self.txo = {} - self.tx_fees = {} - self.pruned_txo = {} - self.save_transactions() with self.lock: - self.history = {} - self.tx_addr_hist = {} + with self.transaction_lock: + self.txi = {} + self.txo = {} + self.tx_fees = {} + self.pruned_txo = {} + self.spent_outpoints = {} + self.history = {} + self.verified_tx = {} + self.transactions = {} + self.save_transactions() @profiler - def build_reverse_history(self): - self.tx_addr_hist = {} - for addr, hist in self.history.items(): - for tx_hash, h in hist: - s = self.tx_addr_hist.get(tx_hash, set()) - s.add(addr) - self.tx_addr_hist[tx_hash] = s + def build_spent_outpoints(self): + self.spent_outpoints = {} + for txid, items in self.txi.items(): + for addr, l in items.items(): + for ser, v in l: + self.spent_outpoints[ser] = txid @profiler def check_history(self): save = False - mine_addrs = list(filter(lambda k: self.is_mine(self.history[k]), self.history.keys())) - if len(mine_addrs) != len(self.history.keys()): + + hist_addrs_mine = list(filter(lambda k: self.is_mine(k), self.history.keys())) + hist_addrs_not_mine = list(filter(lambda k: not self.is_mine(k), self.history.keys())) + + for addr in hist_addrs_not_mine: + self.history.pop(addr) save = True - for addr in mine_addrs: + + for addr in hist_addrs_mine: hist = self.history[addr] for tx_hash, tx_height in hist: @@ -293,9 +346,18 @@ class Abstract_Wallet(PrintError): self.receiving_addresses = d.get('receiving', []) self.change_addresses = d.get('change', []) + def test_addresses_sanity(self): + addrs = self.get_receiving_addresses() + if len(addrs) > 0: + if not bitcoin.is_address(addrs[0]): + raise WalletFileException('The addresses in this wallet are not Zcash addresses.') + def synchronize(self): pass + def is_deterministic(self): + return self.keystore.is_deterministic() + def set_up_to_date(self, up_to_date): with self.lock: self.up_to_date = up_to_date @@ -317,48 +379,67 @@ class Abstract_Wallet(PrintError): if old_text: self.labels.pop(name) changed = True - if changed: run_hook('set_label', self, name, text) self.storage.put('labels', self.labels) - return changed + def set_fiat_value(self, txid, ccy, text): + if txid not in self.transactions: + return + if not text: + d = self.fiat_value.get(ccy, {}) + if d and txid in d: + d.pop(txid) + else: + return + else: + try: + Decimal(text) + except: + return + if ccy not in self.fiat_value: + self.fiat_value[ccy] = {} + self.fiat_value[ccy][txid] = text + self.storage.put('fiat_value', self.fiat_value) + + def get_fiat_value(self, txid, ccy): + fiat_value = self.fiat_value.get(ccy, {}).get(txid) + try: + return Decimal(fiat_value) + except: + return + def is_mine(self, address): return address in self.get_addresses() def is_change(self, address): if not self.is_mine(address): return False - return address in self.change_addresses + return self.get_address_index(address)[0] def get_address_index(self, address): - if address in self.receiving_addresses: - return False, self.receiving_addresses.index(address) - if address in self.change_addresses: - return True, self.change_addresses.index(address) - raise Exception("Address not found", address) + raise NotImplementedError() + + def get_redeem_script(self, address): + return None def export_private_key(self, address, password): - """ extended WIF format """ if self.is_watching_only(): return [] index = self.get_address_index(address) pk, compressed = self.keystore.get_private_key(index, password) - if self.txin_type in ['p2sh']: - pubkeys = self.get_public_keys(address) - redeem_script = self.pubkeys_to_redeem_script(pubkeys) - else: - redeem_script = None - return bitcoin.serialize_privkey(pk, compressed, self.txin_type), redeem_script - + txin_type = self.get_txin_type(address) + redeem_script = self.get_redeem_script(address) + serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type) + return serialized_privkey, redeem_script def get_public_keys(self, address): - sequence = self.get_address_index(address) - return self.get_pubkeys(*sequence) + return [self.get_public_key(address)] def add_unverified_tx(self, tx_hash, tx_height): - if tx_height == 0 and tx_hash in self.verified_tx: + if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT) \ + and tx_hash in self.verified_tx: self.verified_tx.pop(tx_hash) if self.verifier: self.verifier.merkle_roots.pop(tx_hash, None) @@ -398,28 +479,30 @@ class Abstract_Wallet(PrintError): return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0) def get_tx_height(self, tx_hash): - """ return the height and timestamp of a verified transaction. """ + """ Given a transaction, returns (height, conf, timestamp) """ with self.lock: if tx_hash in self.verified_tx: height, timestamp, pos = self.verified_tx[tx_hash] conf = max(self.get_local_height() - height + 1, 0) return height, conf, timestamp - else: + elif tx_hash in self.unverified_tx: height = self.unverified_tx[tx_hash] - return height, 0, False + return height, 0, None + else: + # local transaction + return TX_HEIGHT_LOCAL, 0, None def get_txpos(self, tx_hash): "return position, even if the tx is unverified" with self.lock: - x = self.verified_tx.get(tx_hash) - y = self.unverified_tx.get(tx_hash) - if x: - height, timestamp, pos = x + if tx_hash in self.verified_tx: + height, timestamp, pos = self.verified_tx[tx_hash] return height, pos - elif y > 0: - return y, 0 + elif tx_hash in self.unverified_tx: + height = self.unverified_tx[tx_hash] + return (height, 0) if height > 0 else ((1e9 - height), 0) else: - return 1e12 - y, 0 + return (1e9+1, 0) def is_found(self): return self.history.values() != [[]] * len(self.history) @@ -444,6 +527,17 @@ class Abstract_Wallet(PrintError): delta += v return delta + def get_tx_value(self, txid): + " effect of tx on the entire domain" + delta = 0 + for addr, d in self.txi.get(txid, {}).items(): + for n, v in d: + delta -= v + for addr, d in self.txo.get(txid, {}).items(): + for n, v, cb in d: + delta += v + return delta + def get_wallet_delta(self, tx): """ effect of tx on wallet """ addresses = self.get_addresses() @@ -510,17 +604,20 @@ class Abstract_Wallet(PrintError): height, conf, timestamp = self.get_tx_height(tx_hash) if height > 0: if conf: - status = _("%d confirmations") % conf + status = _("{} confirmations").format(conf) else: status = _('Not verified') - else: + elif height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED): status = _('Unconfirmed') if fee is None: fee = self.tx_fees.get(tx_hash) - if fee and self.network.config.has_fee_estimates(): + if fee and self.network and self.network.config.has_fee_mempool(): size = tx.estimated_size() - fee_per_kb = fee * 1000 / size - exp_n = self.network.config.reverse_dynfee(fee_per_kb) + fee_per_byte = fee / size + exp_n = self.network.config.fee_to_depth(fee_per_byte) + else: + status = _('Local') + can_broadcast = self.network is not None else: status = _("Signed") can_broadcast = self.network is not None @@ -542,7 +639,7 @@ class Abstract_Wallet(PrintError): return tx_hash, status, label, can_broadcast, amount, fee, height, conf, timestamp, exp_n def get_addr_io(self, address): - h = self.history.get(address, []) + h = self.get_address_history(address) received = {} sent = {} for tx_hash, height in h: @@ -583,8 +680,9 @@ class Abstract_Wallet(PrintError): def get_addr_balance(self, address): received, sent = self.get_addr_io(address) c = u = x = 0 + local_height = self.get_local_height() for txo, (tx_height, v, is_cb) in received.items(): - if is_cb and tx_height + COINBASE_MATURITY > self.get_local_height(): + if is_cb and tx_height + COINBASE_MATURITY > local_height: x += v elif tx_height > 0: c += v @@ -641,11 +739,41 @@ class Abstract_Wallet(PrintError): xx += x return cc, uu, xx - def get_address_history(self, address): - with self.lock: - return self.history.get(address, []) + def get_address_history(self, addr): + h = [] + # we need self.transaction_lock but get_tx_height will take self.lock + # so we need to take that too here, to enforce order of locks + with self.lock, self.transaction_lock: + related_txns = self._history_local.get(addr, set()) + for tx_hash in related_txns: + tx_height = self.get_tx_height(tx_hash)[0] + h.append((tx_hash, tx_height)) + return h - def find_pay_to_pubkey_address(self, prevout_hash, prevout_n): + def _add_tx_to_local_history(self, txid): + with self.transaction_lock: + for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])): + cur_hist = self._history_local.get(addr, set()) + cur_hist.add(txid) + self._history_local[addr] = cur_hist + + def _remove_tx_from_local_history(self, txid): + with self.transaction_lock: + for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])): + cur_hist = self._history_local.get(addr, set()) + try: + cur_hist.remove(txid) + except KeyError: + pass + else: + self._history_local[addr] = cur_hist + + def get_txin_address(self, txi): + addr = txi.get('address') + if addr != "(pubkey)": + return addr + prevout_hash = txi.get('prevout_hash') + prevout_n = txi.get('prevout_n') dd = self.txo.get(prevout_hash, {}) for addr, l in dd.items(): for n, v, is_cb in l: @@ -653,21 +781,104 @@ class Abstract_Wallet(PrintError): self.print_error("found pay-to-pubkey address:", addr) return addr - def add_transaction(self, tx_hash, tx): - is_coinbase = len(tx.inputs()) and tx.inputs()[0]['type'] == 'coinbase' + def get_txout_address(self, txo): + _type, x, v = txo + if _type == TYPE_ADDRESS: + addr = x + elif _type == TYPE_PUBKEY: + addr = bitcoin.public_key_to_p2pkh(bfh(x)) + else: + addr = None + return addr + + def get_conflicting_transactions(self, tx): + """Returns a set of transaction hashes from the wallet history that are + directly conflicting with tx, i.e. they have common outpoints being + spent with tx. If the tx is already in wallet history, that will not be + reported as a conflict. + """ + conflicting_txns = set() with self.transaction_lock: + for txi in tx.inputs(): + ser = Transaction.get_outpoint_from_txin(txi) + if ser is None: + continue + spending_tx_hash = self.spent_outpoints.get(ser, None) + if spending_tx_hash is None: + continue + # this outpoint (ser) has already been spent, by spending_tx + assert spending_tx_hash in self.transactions + conflicting_txns |= {spending_tx_hash} + txid = tx.txid() + if txid in conflicting_txns: + # this tx is already in history, so it conflicts with itself + if len(conflicting_txns) > 1: + raise Exception('Found conflicting transactions already in wallet history.') + conflicting_txns -= {txid} + return conflicting_txns + + def add_transaction(self, tx_hash, tx): + assert tx_hash, tx_hash + assert tx, tx + assert tx.is_complete() + # we need self.transaction_lock but get_tx_height will take self.lock + # so we need to take that too here, to enforce order of locks + with self.lock, self.transaction_lock: + # NOTE: returning if tx in self.transactions might seem like a good idea + # BUT we track is_mine inputs in a txn, and during subsequent calls + # of add_transaction tx, we might learn of more-and-more inputs of + # being is_mine, as we roll the gap_limit forward + is_coinbase = tx.inputs()[0]['type'] == 'coinbase' + tx_height = self.get_tx_height(tx_hash)[0] + is_mine = any([self.is_mine(txin['address']) for txin in tx.inputs()]) + # do not save if tx is local and not mine + if tx_height == TX_HEIGHT_LOCAL and not is_mine: + # FIXME the test here should be for "not all is_mine"; cannot detect conflict in some cases + raise NotIsMineTransactionException() + # raise exception if unrelated to wallet + is_for_me = any([self.is_mine(self.get_txout_address(txo)) for txo in tx.outputs()]) + if not is_mine and not is_for_me: + raise UnrelatedTransactionException() + # Find all conflicting transactions. + # In case of a conflict, + # 1. confirmed > mempool > local + # 2. this new txn has priority over existing ones + # When this method exits, there must NOT be any conflict, so + # either keep this txn and remove all conflicting (along with dependencies) + # or drop this txn + conflicting_txns = self.get_conflicting_transactions(tx) + if conflicting_txns: + existing_mempool_txn = any( + self.get_tx_height(tx_hash2)[0] in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT) + for tx_hash2 in conflicting_txns) + existing_confirmed_txn = any( + self.get_tx_height(tx_hash2)[0] > 0 + for tx_hash2 in conflicting_txns) + if existing_confirmed_txn and tx_height <= 0: + # this is a non-confirmed tx that conflicts with confirmed txns; drop. + return False + if existing_mempool_txn and tx_height == TX_HEIGHT_LOCAL: + # this is a local tx that conflicts with non-local txns; drop. + return False + # keep this txn and remove all conflicting + to_remove = set() + to_remove |= conflicting_txns + for conflicting_tx_hash in conflicting_txns: + to_remove |= self.get_depending_transactions(conflicting_tx_hash) + for tx_hash2 in to_remove: + self.remove_transaction(tx_hash2) # add inputs self.txi[tx_hash] = d = {} for txi in tx.inputs(): - addr = txi.get('address') + addr = self.get_txin_address(txi) if txi['type'] != 'coinbase': prevout_hash = txi['prevout_hash'] prevout_n = txi['prevout_n'] ser = prevout_hash + ':%d'%prevout_n - if addr == "(pubkey)": - addr = self.find_pay_to_pubkey_address(prevout_hash, prevout_n) - # find value from prev output if addr and self.is_mine(addr): + # we only track is_mine spends + self.spent_outpoints[ser] = tx_hash + # find value from prev output dd = self.txo.get(prevout_hash, {}) for n, v, is_cb in dd.get(addr, []): if n == prevout_n: @@ -677,18 +888,12 @@ class Abstract_Wallet(PrintError): break else: self.pruned_txo[ser] = tx_hash - # add outputs self.txo[tx_hash] = d = {} for n, txo in enumerate(tx.outputs()): + v = txo[2] ser = tx_hash + ':%d'%n - _type, x, v = txo - if _type == TYPE_ADDRESS: - addr = x - elif _type == TYPE_PUBKEY: - addr = bitcoin.public_key_to_p2pkh(bfh(x)) - else: - addr = None + addr = self.get_txout_address(txo) if addr and self.is_mine(addr): if d.get(addr) is None: d[addr] = [] @@ -701,16 +906,30 @@ class Abstract_Wallet(PrintError): if dd.get(addr) is None: dd[addr] = [] dd[addr].append((ser, v)) + self._add_tx_to_local_history(next_tx) + # add to local history + self._add_tx_to_local_history(tx_hash) # save self.transactions[tx_hash] = tx + return True def remove_transaction(self, tx_hash): + with self.transaction_lock: self.print_error("removing tx from history", tx_hash) - #tx = self.transactions.pop(tx_hash) + self.transactions.pop(tx_hash, None) + # undo spent_outpoints that are in txi + for addr, l in self.txi[tx_hash].items(): + for ser, v in l: + self.spent_outpoints.pop(ser, None) + # undo spent_outpoints that are in pruned_txo for ser, hh in list(self.pruned_txo.items()): if hh == tx_hash: + self.spent_outpoints.pop(ser, None) self.pruned_txo.pop(ser) + + self._remove_tx_from_local_history(tx_hash) + # add tx to pruned_txo, and undo the txi addition for next_tx, dd in self.txi.items(): for addr, l in list(dd.items()): @@ -725,34 +944,33 @@ class Abstract_Wallet(PrintError): dd.pop(addr) else: dd[addr] = l - try: - self.txi.pop(tx_hash) - self.txo.pop(tx_hash) - except KeyError: - self.print_error("tx was not in history", tx_hash) + + self.txi.pop(tx_hash, None) + self.txo.pop(tx_hash, None) def receive_tx_callback(self, tx_hash, tx, tx_height): - self.add_transaction(tx_hash, tx) self.add_unverified_tx(tx_hash, tx_height) + self.add_transaction(tx_hash, tx) def receive_history_callback(self, addr, hist, tx_fees): with self.lock: - old_hist = self.history.get(addr, []) + old_hist = self.get_address_history(addr) for tx_hash, height in old_hist: if (tx_hash, height) not in hist: - # remove tx if it's not referenced in histories - self.tx_addr_hist[tx_hash].remove(addr) - if not self.tx_addr_hist[tx_hash]: + # make tx local + self.unverified_tx.pop(tx_hash, None) + self.verified_tx.pop(tx_hash, None) + if self.verifier: + self.verifier.merkle_roots.pop(tx_hash, None) + # but remove completely if not is_mine + if self.txi[tx_hash] == {}: + # FIXME the test here should be for "not all is_mine"; cannot detect conflict in some cases self.remove_transaction(tx_hash) self.history[addr] = hist for tx_hash, tx_height in hist: # add it in case it was previously unconfirmed self.add_unverified_tx(tx_hash, tx_height) - # add reference in tx_addr_hist - s = self.tx_addr_hist.get(tx_hash, set()) - s.add(addr) - self.tx_addr_hist[tx_hash] = s # if addr is new, we have to recompute txi and txo tx = self.transactions.get(tx_hash) if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get(tx_hash, {}).get(addr) is None: @@ -805,6 +1023,115 @@ class Abstract_Wallet(PrintError): return h2 + def balance_at_timestamp(self, domain, target_timestamp): + h = self.get_history(domain) + for tx_hash, height, conf, timestamp, value, balance in h: + if timestamp > target_timestamp: + return balance - value + # return last balance + return balance + + @profiler + def get_full_history(self, domain=None, from_timestamp=None, to_timestamp=None, fx=None, show_addresses=False): + from .util import timestamp_to_datetime, Satoshis, Fiat + out = [] + income = 0 + expenditures = 0 + capital_gains = Decimal(0) + fiat_income = Decimal(0) + fiat_expenditures = Decimal(0) + h = self.get_history(domain) + for tx_hash, height, conf, timestamp, value, balance in h: + if from_timestamp and (timestamp or time.time()) < from_timestamp: + continue + if to_timestamp and (timestamp or time.time()) >= to_timestamp: + continue + item = { + 'txid':tx_hash, + 'height':height, + 'confirmations':conf, + 'timestamp':timestamp, + 'value': Satoshis(value), + 'balance': Satoshis(balance) + } + item['date'] = timestamp_to_datetime(timestamp) + item['label'] = self.get_label(tx_hash) + if show_addresses: + tx = self.transactions.get(tx_hash) + tx.deserialize() + input_addresses = [] + output_addresses = [] + for x in tx.inputs(): + if x['type'] == 'coinbase': continue + addr = self.get_txin_address(x) + if addr is None: + continue + input_addresses.append(addr) + for addr, v in tx.get_outputs(): + output_addresses.append(addr) + item['input_addresses'] = input_addresses + item['output_addresses'] = output_addresses + # value may be None if wallet is not fully synchronized + if value is None: + continue + # fixme: use in and out values + if value < 0: + expenditures += -value + else: + income += value + # fiat computations + if fx and fx.is_enabled(): + date = timestamp_to_datetime(timestamp) + fiat_value = self.get_fiat_value(tx_hash, fx.ccy) + fiat_default = fiat_value is None + fiat_value = fiat_value if fiat_value is not None else value / Decimal(COIN) * self.price_at_timestamp(tx_hash, fx.timestamp_rate) + item['fiat_value'] = Fiat(fiat_value, fx.ccy) + item['fiat_default'] = fiat_default + if value < 0: + acquisition_price = - value / Decimal(COIN) * self.average_price(tx_hash, fx.timestamp_rate, fx.ccy) + liquidation_price = - fiat_value + item['acquisition_price'] = Fiat(acquisition_price, fx.ccy) + cg = liquidation_price - acquisition_price + item['capital_gain'] = Fiat(cg, fx.ccy) + capital_gains += cg + fiat_expenditures += -fiat_value + else: + fiat_income += fiat_value + out.append(item) + # add summary + if out: + b, v = out[0]['balance'].value, out[0]['value'].value + start_balance = None if b is None or v is None else b - v + end_balance = out[-1]['balance'].value + if from_timestamp is not None and to_timestamp is not None: + start_date = timestamp_to_datetime(from_timestamp) + end_date = timestamp_to_datetime(to_timestamp) + else: + start_date = None + end_date = None + summary = { + 'start_date': start_date, + 'end_date': end_date, + 'start_balance': Satoshis(start_balance), + 'end_balance': Satoshis(end_balance), + 'income': Satoshis(income), + 'expenditures': Satoshis(expenditures) + } + if fx and fx.is_enabled(): + unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy) + summary['capital_gains'] = Fiat(capital_gains, fx.ccy) + summary['fiat_income'] = Fiat(fiat_income, fx.ccy) + summary['fiat_expenditures'] = Fiat(fiat_expenditures, fx.ccy) + summary['unrealized_gains'] = Fiat(unrealized, fx.ccy) + summary['start_fiat_balance'] = Fiat(fx.historical_value(start_balance, start_date), fx.ccy) + summary['end_fiat_balance'] = Fiat(fx.historical_value(end_balance, end_date), fx.ccy) + else: + summary = {} + return { + 'transactions': out, + 'summary': summary + } + def get_label(self, tx_hash): label = self.labels.get(tx_hash, '') if label is '': @@ -824,32 +1151,37 @@ class Abstract_Wallet(PrintError): def get_tx_status(self, tx_hash, height, conf, timestamp): from .util import format_time + extra = [] if conf == 0: tx = self.transactions.get(tx_hash) if not tx: - return 3, 'unknown' - is_final = tx and tx.is_final() - fee = self.tx_fees.get(tx_hash) - if fee and self.network and self.network.config.has_fee_estimates(): - size = len(tx.raw)/2 - low_fee = int(self.network.config.dynfee(0)*size/1000) - is_lowfee = fee < low_fee * 0.5 - else: - is_lowfee = False - if height==0 and not is_final: - status = 0 - elif height < 0: - status = 1 - elif height == 0 and is_lowfee: - status = 2 - elif height == 0: + return 2, 'unknown' + fee = self.get_wallet_delta(tx)[3] + if fee is None: + fee = self.tx_fees.get(tx_hash) + if fee is not None: + size = tx.estimated_size() + fee_per_byte = fee / size + extra.append('%.1f sat/b'%(fee_per_byte)) + if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \ + and self.network and self.network.config.has_fee_mempool(): + exp_n = self.network.config.fee_to_depth(fee_per_byte) + if exp_n: + extra.append('%.2f MB'%(exp_n/1000000)) + if height == TX_HEIGHT_LOCAL: status = 3 + elif height == TX_HEIGHT_UNCONF_PARENT: + status = 1 + elif height == TX_HEIGHT_UNCONFIRMED: + status = 0 else: - status = 4 + status = 2 else: - status = 4 + min(conf, 6) + status = 3 + min(conf, 6) time_str = format_time(timestamp) if timestamp else _("unknown") - status_str = TX_STATUS[status] if status < 5 else time_str + status_str = TX_STATUS[status] if status < 4 else time_str + if extra: + status_str += ' [%s]'%(', '.join(extra)) return status, status_str def relayfee(self): @@ -866,10 +1198,10 @@ class Abstract_Wallet(PrintError): _type, data, value = o if _type == TYPE_ADDRESS: if not is_address(data): - raise BaseException("Invalid Zcash address:" + data) + raise Exception("Invalid Zcash address: {}".format(data)) if value == '!': if i_max is not None: - raise BaseException("More than one output set to spend max") + raise Exception("More than one output set to spend max") i_max = i # Avoid index-out-of-range with inputs[0] below @@ -877,10 +1209,11 @@ class Abstract_Wallet(PrintError): raise NotEnoughFunds() if fixed_fee is None and config.fee_per_kb() is None: - raise BaseException('Dynamic fee estimates not available') + raise NoDynamicFeeEstimates() - for item in inputs: - self.add_input_info(item) + if not is_sweep: + for item in inputs: + self.add_input_info(item) # change address if change_addr: @@ -896,13 +1229,18 @@ class Abstract_Wallet(PrintError): if not change_addrs: change_addrs = [random.choice(addrs)] else: - change_addrs = [inputs[0]['address']] + # coin_chooser will set change address + change_addrs = [] # Fee estimator if fixed_fee is None: fee_estimator = config.estimate_fee - else: + elif isinstance(fixed_fee, Number): fee_estimator = lambda size: fixed_fee + elif callable(fixed_fee): + fee_estimator = fixed_fee + else: + raise Exception('Invalid argument fixed_fee: %s' % fixed_fee) if i_max is None: # Let the coin chooser select the coins to spend @@ -911,6 +1249,7 @@ class Abstract_Wallet(PrintError): tx = coin_chooser.make_tx(inputs, outputs, change_addrs[:max_change], fee_estimator, self.dust_threshold()) else: + # FIXME?? this might spend inputs with negative effective value... sendable = sum(map(lambda x:x['value'], inputs)) _type, data, value = outputs[i_max] outputs[i_max] = (_type, data, 0) @@ -947,25 +1286,16 @@ class Abstract_Wallet(PrintError): return True return False - def prepare_for_verifier(self): + def load_unverified_transactions(self): # review transactions that are in the history for addr, hist in self.history.items(): for tx_hash, tx_height in hist: # add it in case it was previously unconfirmed self.add_unverified_tx(tx_hash, tx_height) - # if we are on a pruning server, remove unverified transactions - with self.lock: - vr = list(self.verified_tx.keys()) + list(self.unverified_tx.keys()) - for tx_hash in list(self.transactions): - if tx_hash not in vr: - self.print_error("removing transaction", tx_hash) - self.transactions.pop(tx_hash) - def start_threads(self, network): self.network = network if self.network is not None: - self.prepare_for_verifier() self.verifier = SPV(self.network, self) self.synchronizer = Synchronizer(self, network) network.add_jobs([self.verifier, self.synchronizer]) @@ -979,7 +1309,7 @@ class Abstract_Wallet(PrintError): self.synchronizer.release() self.synchronizer = None self.verifier = None - # Now no references to the syncronizer or verifier + # Now no references to the synchronizer or verifier # remain so they will be GC-ed self.storage.put('stored_height', self.get_local_height()) self.save_transactions() @@ -1016,8 +1346,10 @@ class Abstract_Wallet(PrintError): def is_used(self, address): h = self.history.get(address,[]) + if len(h) == 0: + return False c, u, x = self.get_addr_balance(address) - return len(h) > 0 and c + u + x == 0 + return c + u + x == 0 def is_empty(self, address): c, u, x = self.get_addr_balance(address) @@ -1027,7 +1359,7 @@ class Abstract_Wallet(PrintError): age = -1 h = self.history.get(address, []) for tx_hash, tx_height in h: - if tx_height == 0: + if tx_height <= 0: tx_age = 0 else: tx_age = self.get_local_height() - tx_height + 1 @@ -1053,10 +1385,14 @@ class Abstract_Wallet(PrintError): # First look up an input transaction in the wallet where it # will likely be. If co-signing a transaction it may not have # all the input txs, in which case we ask the network. - tx = self.transactions.get(tx_hash) + tx = self.transactions.get(tx_hash, None) if not tx and self.network: request = ('blockchain.transaction.get', [tx_hash]) - tx = Transaction(self.network.synchronous_get(request)) + try: + tx = Transaction(self.network.synchronous_get(request)) + except TimeoutException as e: + self.print_error('getting input txn from network timed out for {}'.format(tx_hash)) + raise e return tx def add_hw_info(self, tx): @@ -1069,7 +1405,7 @@ class Abstract_Wallet(PrintError): xpubs = self.get_master_public_keys() for txout in tx.outputs(): _type, addr, amount = txout - if self.is_change(addr): + if self.is_mine(addr): index = self.get_address_index(addr) pubkeys = self.get_public_keys(addr) # sort xpubs using the order of pubkeys @@ -1083,8 +1419,8 @@ class Abstract_Wallet(PrintError): # hardware wallets require extra info if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]): self.add_hw_info(tx) - # sign - for k in self.get_keystores(): + # sign. start with ready keystores. + for k in sorted(self.get_keystores(), key=lambda ks: ks.ready_to_sign(), reverse=True): try: if k.can_sign(tx): k.sign_transaction(tx, password) @@ -1156,7 +1492,10 @@ class Abstract_Wallet(PrintError): baseurl = 'file://' + rdir rewrite = config.get('url_rewrite') if rewrite: - baseurl = baseurl.replace(*rewrite) + try: + baseurl = baseurl.replace(*rewrite) + except BaseException as e: + self.print_stderr('Invalid config setting for "url_rewrite". err:', e) out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key) out['URI'] += '&r=' + out['request_url'] out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key @@ -1215,6 +1554,11 @@ class Abstract_Wallet(PrintError): def add_payment_request(self, req, config): addr = req['address'] + if not bitcoin.is_address(addr): + raise Exception(_('Invalid Zcash address.')) + if not self.is_mine(addr): + raise Exception(_('Address not in wallet.')) + amount = req.get('amount') message = req.get('memo') self.receive_requests[addr] = req @@ -1236,7 +1580,7 @@ class Abstract_Wallet(PrintError): f.write(pr.SerializeToString()) # reload req = self.get_payment_request(addr, config) - with open(os.path.join(path, key + '.json'), 'w') as f: + with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f: f.write(json.dumps(req)) return req @@ -1255,13 +1599,14 @@ class Abstract_Wallet(PrintError): return True def get_sorted_requests(self, config): - def f(x): + def f(addr): try: - addr = x.get('address') - return self.get_address_index(addr) or addr + return self.get_address_index(addr) except: - return addr - return sorted(map(lambda x: self.get_payment_request(x, config), self.receive_requests.keys()), key=f) + return + keys = map(lambda x: (f(x), x), self.receive_requests.keys()) + sorted_keys = sorted(filter(lambda x: x[0] is not None, keys)) + return [self.get_payment_request(x[1], config) for x in sorted_keys] def get_fingerprint(self): raise NotImplementedError() @@ -1282,10 +1627,65 @@ class Abstract_Wallet(PrintError): self.synchronizer.add(address) def has_password(self): - return self.storage.get('use_encryption', False) + return self.has_keystore_encryption() or self.has_storage_encryption() + + def can_have_keystore_encryption(self): + return self.keystore and self.keystore.may_have_password() + + def get_available_storage_encryption_version(self): + """Returns the type of storage encryption offered to the user. + + A wallet file (storage) is either encrypted with this version + or is stored in plaintext. + """ + if isinstance(self.keystore, Hardware_KeyStore): + return STO_EV_XPUB_PW + else: + return STO_EV_USER_PW + + def has_keystore_encryption(self): + """Returns whether encryption is enabled for the keystore. + + If True, e.g. signing a transaction will require a password. + """ + if self.can_have_keystore_encryption(): + return self.storage.get('use_encryption', False) + return False + + def has_storage_encryption(self): + """Returns whether encryption is enabled for the wallet file on disk.""" + return self.storage.is_encrypted() + + @classmethod + def may_have_password(cls): + return True def check_password(self, password): - self.keystore.check_password(password) + if self.has_keystore_encryption(): + self.keystore.check_password(password) + self.storage.check_password(password) + + def update_password(self, old_pw, new_pw, encrypt_storage=False): + if old_pw is None and self.has_password(): + raise InvalidPassword() + self.check_password(old_pw) + + if encrypt_storage: + enc_version = self.get_available_storage_encryption_version() + else: + enc_version = STO_EV_PLAINTEXT + self.storage.set_password(new_pw, enc_version) + + # note: Encrypting storage with a hw device is currently only + # allowed for non-multisig wallets. Further, + # Hardware_KeyStore.may_have_password() == False. + # If these were not the case, + # extra care would need to be taken when encrypting keystores. + self._update_password_for_keystore(old_pw, new_pw) + encrypt_keystore = self.can_have_keystore_encryption() + self.storage.set_keystore_encryption(bool(new_pw) and encrypt_keystore) + + self.storage.write() def sign_message(self, address, message, password): index = self.get_address_index(address) @@ -1296,6 +1696,72 @@ class Abstract_Wallet(PrintError): index = self.get_address_index(addr) return self.keystore.decrypt_message(index, message, password) + def get_depending_transactions(self, tx_hash): + """Returns all (grand-)children of tx_hash in this wallet.""" + children = set() + for other_hash, tx in self.transactions.items(): + for input in (tx.inputs()): + if input["prevout_hash"] == tx_hash: + children.add(other_hash) + children |= self.get_depending_transactions(other_hash) + return children + + def txin_value(self, txin): + txid = txin['prevout_hash'] + prev_n = txin['prevout_n'] + for address, d in self.txo.get(txid, {}).items(): + for n, v, cb in d: + if n == prev_n: + return v + # may occur if wallet is not synchronized + return None + + def price_at_timestamp(self, txid, price_func): + """Returns fiat price of bitcoin at the time tx got confirmed.""" + height, conf, timestamp = self.get_tx_height(txid) + return price_func(timestamp if timestamp else time.time()) + + def unrealized_gains(self, domain, price_func, ccy): + coins = self.get_utxos(domain) + now = time.time() + p = price_func(now) + ap = sum(self.coin_price(coin['prevout_hash'], price_func, ccy, self.txin_value(coin)) for coin in coins) + lp = sum([coin['value'] for coin in coins]) * p / Decimal(COIN) + return lp - ap + + def average_price(self, txid, price_func, ccy): + """ Average acquisition price of the inputs of a transaction """ + input_value = 0 + total_price = 0 + for addr, d in self.txi.get(txid, {}).items(): + for ser, v in d: + input_value += v + total_price += self.coin_price(ser.split(':')[0], price_func, ccy, v) + return total_price / (input_value/Decimal(COIN)) + + def coin_price(self, txid, price_func, ccy, txin_value): + """ + Acquisition price of a coin. + This assumes that either all inputs are mine, or no input is mine. + """ + if txin_value is None: + return Decimal('NaN') + cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value)) + result = self.coin_price_cache.get(cache_key, None) + if result is not None: + return result + if self.txi.get(txid, {}) != {}: + result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) + self.coin_price_cache[cache_key] = result + return result + else: + fiat_value = self.get_fiat_value(txid, ccy) + if fiat_value is not None: + return fiat_value + else: + p = self.price_at_timestamp(txid, price_func) + return p * txin_value/Decimal(COIN) + class Simple_Wallet(Abstract_Wallet): # wallet with a single keystore @@ -1309,16 +1775,10 @@ class Simple_Wallet(Abstract_Wallet): def is_watching_only(self): return self.keystore.is_watching_only() - def can_change_password(self): - return self.keystore.can_change_password() - - def update_password(self, old_pw, new_pw, encrypt=False): - if old_pw is None and self.has_password(): - raise InvalidPassword() - self.keystore.update_password(old_pw, new_pw) - self.save_keystore() - self.storage.set_password(new_pw, encrypt) - self.storage.write() + def _update_password_for_keystore(self, old_pw, new_pw): + if self.keystore and self.keystore.may_have_password(): + self.keystore.update_password(old_pw, new_pw) + self.save_keystore() def save_keystore(self): self.storage.put('keystore', self.keystore.dump()) @@ -1357,9 +1817,6 @@ class Imported_Wallet(Simple_Wallet): def save_addresses(self): self.storage.put('addresses', self.addresses) - def can_change_password(self): - return not self.is_watching_only() - def can_import_address(self): return self.is_watching_only() @@ -1378,9 +1835,12 @@ class Imported_Wallet(Simple_Wallet): def get_master_public_keys(self): return [] - def is_beyond_limit(self, address, is_change): + def is_beyond_limit(self, address): return False + def is_mine(self, address): + return address in self.addresses + def get_fingerprint(self): return '' @@ -1439,8 +1899,18 @@ class Imported_Wallet(Simple_Wallet): pubkey = self.get_public_key(address) self.addresses.pop(address) if pubkey: - self.keystore.delete_imported_key(pubkey) - self.save_keystore() + # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key) + for txin_type in bitcoin.SCRIPT_TYPES.keys(): + try: + addr2 = bitcoin.pubkey_to_address(txin_type, pubkey) + except NotImplementedError: + pass + else: + if addr2 in self.addresses: + break + else: + self.keystore.delete_imported_key(pubkey) + self.save_keystore() self.storage.put('addresses', self.addresses) self.storage.write() @@ -1455,14 +1925,15 @@ class Imported_Wallet(Simple_Wallet): try: txin_type, pubkey = self.keystore.import_privkey(sec, pw) except Exception: - raise BaseException('Invalid private key', sec) + neutered_privkey = str(sec)[:3] + '..' + str(sec)[-2:] + raise BitcoinException('Invalid private key: {}'.format(neutered_privkey)) if txin_type in ['p2pkh']: if redeem_script is not None: - raise BaseException('Cannot use redeem script with', txin_type, sec) + 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']: if redeem_script is None: - raise BaseException('Redeem script required for', txin_type, sec) + 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) @@ -1473,12 +1944,10 @@ class Imported_Wallet(Simple_Wallet): self.add_address(addr) return addr - def export_private_key(self, address, password): + def get_redeem_script(self, address): d = self.addresses[address] - pubkey = d['pubkey'] redeem_script = d['redeem_script'] - sec = pw_decode(self.keystore.keypairs[pubkey], password) - return sec, redeem_script + return redeem_script def get_txin_type(self, address): return self.addresses[address].get('type', 'address') @@ -1516,9 +1985,6 @@ class Deterministic_Wallet(Abstract_Wallet): def has_seed(self): return self.keystore.has_seed() - def is_deterministic(self): - return self.keystore.is_deterministic() - def get_receiving_addresses(self): return self.receiving_addresses @@ -1570,16 +2036,26 @@ class Deterministic_Wallet(Abstract_Wallet): if n > nmax: nmax = n return nmax + 1 + def load_addresses(self): + super().load_addresses() + self._addr_to_addr_index = {} # key: address, value: (is_change, index) + for i, addr in enumerate(self.receiving_addresses): + self._addr_to_addr_index[addr] = (False, i) + for i, addr in enumerate(self.change_addresses): + self._addr_to_addr_index[addr] = (True, i) + def create_new_address(self, for_change=False): assert type(for_change) is bool - addr_list = self.change_addresses if for_change else self.receiving_addresses - n = len(addr_list) - x = self.derive_pubkeys(for_change, n) - address = self.pubkeys_to_address(x) - addr_list.append(address) - self.save_addresses() - self.add_address(address) - return address + with self.lock: + addr_list = self.change_addresses if for_change else self.receiving_addresses + n = len(addr_list) + x = self.derive_pubkeys(for_change, n) + address = self.pubkeys_to_address(x) + addr_list.append(address) + self._addr_to_addr_index[address] = (for_change, n) + self.save_addresses() + self.add_address(address) + return address def synchronize_sequence(self, for_change): limit = self.gap_limit_for_change if for_change else self.gap_limit @@ -1595,30 +2071,27 @@ class Deterministic_Wallet(Abstract_Wallet): def synchronize(self): with self.lock: - if self.is_deterministic(): - self.synchronize_sequence(False) - self.synchronize_sequence(True) - else: - if len(self.receiving_addresses) != len(self.keystore.keypairs): - pubkeys = self.keystore.keypairs.keys() - self.receiving_addresses = [self.pubkeys_to_address(i) for i in pubkeys] - self.save_addresses() - for addr in self.receiving_addresses: - self.add_address(addr) + self.synchronize_sequence(False) + self.synchronize_sequence(True) - def is_beyond_limit(self, address, is_change): + def is_beyond_limit(self, address): + is_change, i = self.get_address_index(address) addr_list = self.get_change_addresses() if is_change else self.get_receiving_addresses() - i = addr_list.index(address) - prev_addresses = addr_list[:max(0, i)] limit = self.gap_limit_for_change if is_change else self.gap_limit - if len(prev_addresses) < limit: + if i < limit: return False - prev_addresses = prev_addresses[max(0, i - limit):] + prev_addresses = addr_list[max(0, i - limit):max(0, i)] for addr in prev_addresses: if self.history.get(addr): return False return True + def is_mine(self, address): + return address in self._addr_to_addr_index + + def get_address_index(self, address): + return self._addr_to_addr_index[address] + def get_master_public_keys(self): return [self.get_master_public_key()] @@ -1652,9 +2125,6 @@ class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet): def get_pubkey(self, c, i): return self.derive_pubkeys(c, i) - def get_public_keys(self, address): - return [self.get_public_key(address)] - def add_input_sig_info(self, txin, address): derivation = self.get_address_index(address) x_pubkey = self.keystore.get_xpubkey(*derivation) @@ -1692,6 +2162,10 @@ class Multisig_Wallet(Deterministic_Wallet): def get_pubkeys(self, c, i): return self.derive_pubkeys(c, i) + def get_public_keys(self, address): + sequence = self.get_address_index(address) + return self.get_pubkeys(*sequence) + def pubkeys_to_address(self, pubkeys): redeem_script = self.pubkeys_to_redeem_script(pubkeys) return bitcoin.redeem_script_to_address(self.txin_type, redeem_script) @@ -1699,6 +2173,11 @@ class Multisig_Wallet(Deterministic_Wallet): def pubkeys_to_redeem_script(self, pubkeys): return transaction.multisig_script(sorted(pubkeys), self.m) + def get_redeem_script(self, address): + pubkeys = self.get_public_keys(address) + redeem_script = self.pubkeys_to_redeem_script(pubkeys) + return redeem_script + def derive_pubkeys(self, c, i): return [k.derive_pubkey(c, i) for k in self.get_keystores()] @@ -1721,22 +2200,28 @@ class Multisig_Wallet(Deterministic_Wallet): def get_keystores(self): return [self.keystores[i] for i in sorted(self.keystores.keys())] - def update_password(self, old_pw, new_pw, encrypt=False): - if old_pw is None and self.has_password(): - raise InvalidPassword() + def can_have_keystore_encryption(self): + return any([k.may_have_password() for k in self.get_keystores()]) + + def _update_password_for_keystore(self, old_pw, new_pw): for name, keystore in self.keystores.items(): - if keystore.can_change_password(): + if keystore.may_have_password(): keystore.update_password(old_pw, new_pw) self.storage.put(name, keystore.dump()) - self.storage.set_password(new_pw, encrypt) - self.storage.write() + + def check_password(self, password): + for name, keystore in self.keystores.items(): + if keystore.may_have_password(): + keystore.check_password(password) + self.storage.check_password(password) + + def get_available_storage_encryption_version(self): + # multisig wallets are not offered hw device encryption + return STO_EV_USER_PW def has_seed(self): return self.keystore.has_seed() - def can_change_password(self): - return self.keystore.can_change_password() - def is_watching_only(self): return not any([not k.is_watching_only() for k in self.get_keystores()]) @@ -1802,5 +2287,5 @@ class Wallet(object): return Multisig_Wallet if wallet_type in wallet_constructors: return wallet_constructors[wallet_type] - raise RuntimeError("Unknown wallet type: " + wallet_type) + raise RuntimeError("Unknown wallet type: " + str(wallet_type)) diff --git a/lib/websockets.py b/lib/websockets.py index 415556b3..587119f9 100644 --- a/lib/websockets.py +++ b/lib/websockets.py @@ -64,7 +64,7 @@ class WsClientThread(util.DaemonThread): # read json file rdir = self.config.get('requests_dir') n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json') - with open(n) as f: + with open(n, encoding='utf-8') as f: s = f.read() d = json.loads(s) addr = d.get('address') diff --git a/lib/www/example.php b/lib/www/example.php deleted file mode 100644 index be981b96..00000000 --- a/lib/www/example.php +++ /dev/null @@ -1,17 +0,0 @@ -Wallet balance
'."\n"; -try { - - $balance = $electrum->getbalance(); - echo 'confirmed: '.$balance['confirmed'].'
'."\n"; - echo 'unconfirmed: '.$balance['unconfirmed'].'
'."\n"; - -} catch (Exception $e) { - echo nl2br($e->getMessage()).'
'."\n"; -} - -?> diff --git a/lib/x509.py b/lib/x509.py index 52dd45f5..e4b94d40 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -284,7 +284,7 @@ class X509(object): return self.AKI if self.AKI else repr(self.issuer) def get_common_name(self): - return self.subject.get('2.5.4.3', 'unknown').decode() + return self.subject.get('2.5.4.3', b'unknown').decode() def get_signature(self): return self.cert_sig_algo, self.signature, self.data @@ -313,7 +313,7 @@ def load_certificates(ca_path): ca_list = {} ca_keyID = {} # ca_path = '/tmp/tmp.txt' - with open(ca_path, 'r') as f: + with open(ca_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") for b in bList: