mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
wallet: make importing thousands of addr/privkeys fast
fixes #3101 closes #3106 closes #3113
This commit is contained in:
parent
917b7fa898
commit
34569d172f
5 changed files with 84 additions and 54 deletions
|
@ -189,17 +189,23 @@ class BaseWizard(object):
|
|||
# will be reflected on self.storage
|
||||
if keystore.is_address_list(text):
|
||||
w = Imported_Wallet(self.storage)
|
||||
for x in text.split():
|
||||
w.import_address(x)
|
||||
addresses = text.split()
|
||||
good_inputs, bad_inputs = w.import_addresses(addresses)
|
||||
elif keystore.is_private_key_list(text):
|
||||
k = keystore.Imported_KeyStore({})
|
||||
self.storage.put('keystore', k.dump())
|
||||
w = Imported_Wallet(self.storage)
|
||||
for x in keystore.get_private_keys(text):
|
||||
w.import_private_key(x, None)
|
||||
keys = keystore.get_private_keys(text)
|
||||
good_inputs, bad_inputs = w.import_private_keys(keys, None)
|
||||
self.keystores.append(w.keystore)
|
||||
else:
|
||||
return self.terminate()
|
||||
if bad_inputs:
|
||||
msg = "\n".join(f"{key[:10]}... ({msg})" for key, msg in bad_inputs[:10])
|
||||
if len(bad_inputs) > 10: msg += '\n...'
|
||||
self.show_error(_("The following inputs could not be imported")
|
||||
+ f' ({len(bad_inputs)}):\n' + msg)
|
||||
# FIXME what if len(good_inputs) == 0 ?
|
||||
return self.run('create_wallet')
|
||||
|
||||
def restore_from_key(self):
|
||||
|
|
|
@ -166,14 +166,20 @@ class Commands:
|
|||
text = text.strip()
|
||||
if keystore.is_address_list(text):
|
||||
wallet = Imported_Wallet(storage)
|
||||
for x in text.split():
|
||||
wallet.import_address(x)
|
||||
addresses = text.split()
|
||||
good_inputs, bad_inputs = wallet.import_addresses(addresses)
|
||||
# FIXME tell user about bad_inputs
|
||||
if not good_inputs:
|
||||
raise Exception("None of the given addresses can be imported")
|
||||
elif keystore.is_private_key_list(text, allow_spaces_inside_key=False):
|
||||
k = keystore.Imported_KeyStore({})
|
||||
storage.put('keystore', k.dump())
|
||||
wallet = Imported_Wallet(storage)
|
||||
for x in text.split():
|
||||
wallet.import_private_key(x, password)
|
||||
keys = keystore.get_private_keys(text)
|
||||
good_inputs, bad_inputs = wallet.import_private_keys(keys, password)
|
||||
# FIXME tell user about bad_inputs
|
||||
if not good_inputs:
|
||||
raise Exception("None of the given privkeys can be imported")
|
||||
else:
|
||||
if keystore.is_seed(text):
|
||||
k = keystore.from_seed(text, passphrase)
|
||||
|
@ -435,7 +441,7 @@ class Commands:
|
|||
try:
|
||||
addr = self.wallet.import_private_key(privkey, password)
|
||||
out = "Keypair imported: " + addr
|
||||
except BaseException as e:
|
||||
except Exception as e:
|
||||
out = "Error: " + str(e)
|
||||
return out
|
||||
|
||||
|
|
|
@ -2612,19 +2612,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
|
||||
if not text:
|
||||
return
|
||||
bad = []
|
||||
good = []
|
||||
for key in str(text).split():
|
||||
try:
|
||||
addr = func(key)
|
||||
good.append(addr)
|
||||
except BaseException as e:
|
||||
bad.append(key)
|
||||
continue
|
||||
if good:
|
||||
self.show_message(_("The following addresses were added") + ':\n' + '\n'.join(good))
|
||||
if bad:
|
||||
self.show_critical(_("The following inputs could not be imported") + ':\n'+ '\n'.join(bad))
|
||||
keys = str(text).split()
|
||||
good_inputs, bad_inputs = func(keys)
|
||||
if good_inputs:
|
||||
msg = '\n'.join(good_inputs[:10])
|
||||
if len(good_inputs) > 10: msg += '\n...'
|
||||
self.show_message(_("The following addresses were added")
|
||||
+ f' ({len(good_inputs)}):\n' + msg)
|
||||
if bad_inputs:
|
||||
msg = "\n".join(f"{key[:10]}... ({msg})" for key, msg in bad_inputs[:10])
|
||||
if len(bad_inputs) > 10: msg += '\n...'
|
||||
self.show_error(_("The following inputs could not be imported")
|
||||
+ f' ({len(bad_inputs)}):\n' + msg)
|
||||
self.address_list.update()
|
||||
self.history_list.update()
|
||||
|
||||
|
@ -2632,7 +2631,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
if not self.wallet.can_import_address():
|
||||
return
|
||||
title, msg = _('Import addresses'), _("Enter addresses")+':'
|
||||
self._do_import(title, msg, self.wallet.import_address)
|
||||
self._do_import(title, msg, self.wallet.import_addresses)
|
||||
|
||||
@protected
|
||||
def do_import_privkey(self, password):
|
||||
|
@ -2642,7 +2641,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
header_layout = QHBoxLayout()
|
||||
header_layout.addWidget(QLabel(_("Enter private keys")+':'))
|
||||
header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
|
||||
self._do_import(title, header_layout, lambda x: self.wallet.import_private_key(x, password))
|
||||
self._do_import(title, header_layout, lambda x: self.wallet.import_private_keys(x, password))
|
||||
|
||||
def update_fiat(self):
|
||||
b = self.fx and self.fx.is_enabled()
|
||||
|
|
|
@ -1158,7 +1158,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
@mock.patch.object(storage.WalletStorage, '_write')
|
||||
def test_sending_offline_wif_online_addr_p2pkh(self, mock_write): # compressed pubkey
|
||||
wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True)
|
||||
wallet_offline.import_private_key('p2pkh:cQDxbmQfwRV3vP1mdnVHq37nJekHLsuD3wdSQseBRA2ct4MFk5Pq', pw=None)
|
||||
wallet_offline.import_private_key('p2pkh:cQDxbmQfwRV3vP1mdnVHq37nJekHLsuD3wdSQseBRA2ct4MFk5Pq', password=None)
|
||||
wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
|
||||
wallet_online.import_address('mg2jk6S5WGDhUPA8mLSxDLWpUoQnX1zzoG')
|
||||
|
||||
|
@ -1192,7 +1192,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
@mock.patch.object(storage.WalletStorage, '_write')
|
||||
def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_write):
|
||||
wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True)
|
||||
wallet_offline.import_private_key('p2wpkh-p2sh:cU9hVzhpvfn91u2zTVn8uqF2ymS7ucYH8V5TmsTDmuyMHgRk9WsJ', pw=None)
|
||||
wallet_offline.import_private_key('p2wpkh-p2sh:cU9hVzhpvfn91u2zTVn8uqF2ymS7ucYH8V5TmsTDmuyMHgRk9WsJ', password=None)
|
||||
wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
|
||||
wallet_online.import_address('2NA2JbUVK7HGWUCK5RXSVNHrkgUYF8d9zV8')
|
||||
|
||||
|
@ -1226,7 +1226,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
@mock.patch.object(storage.WalletStorage, '_write')
|
||||
def test_sending_offline_wif_online_addr_p2wpkh(self, mock_write):
|
||||
wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True)
|
||||
wallet_offline.import_private_key('p2wpkh:cPuQzcNEgbeYZ5at9VdGkCwkPA9r34gvEVJjuoz384rTfYpahfe7', pw=None)
|
||||
wallet_offline.import_private_key('p2wpkh:cPuQzcNEgbeYZ5at9VdGkCwkPA9r34gvEVJjuoz384rTfYpahfe7', password=None)
|
||||
wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
|
||||
wallet_online.import_address('tb1qm2eh4787lwanrzr6pf0ekf5c7jnmghm2y9k529')
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ import traceback
|
|||
from functools import partial
|
||||
from numbers import Number
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
|
||||
from .i18n import _
|
||||
from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,
|
||||
|
@ -1227,16 +1227,29 @@ class Imported_Wallet(Simple_Wallet):
|
|||
def get_change_addresses(self):
|
||||
return []
|
||||
|
||||
def import_address(self, address):
|
||||
if not bitcoin.is_address(address):
|
||||
return ''
|
||||
if address in self.addresses:
|
||||
return ''
|
||||
self.addresses[address] = {}
|
||||
self.add_address(address)
|
||||
def import_addresses(self, addresses: List[str]) -> Tuple[List[str], List[Tuple[str, str]]]:
|
||||
good_addr = [] # type: List[str]
|
||||
bad_addr = [] # type: List[Tuple[str, str]]
|
||||
for address in addresses:
|
||||
if not bitcoin.is_address(address):
|
||||
bad_addr.append((address, _('invalid address')))
|
||||
continue
|
||||
if address in self.addresses:
|
||||
bad_addr.append((address, _('address already in wallet')))
|
||||
continue
|
||||
good_addr.append(address)
|
||||
self.addresses[address] = {}
|
||||
self.add_address(address)
|
||||
self.save_addresses()
|
||||
self.save_transactions(write=True)
|
||||
return address
|
||||
return good_addr, bad_addr
|
||||
|
||||
def import_address(self, address: str) -> str:
|
||||
good_addr, bad_addr = self.import_addresses([address])
|
||||
if good_addr and good_addr[0] == address:
|
||||
return address
|
||||
else:
|
||||
raise BitcoinException(str(bad_addr[0][1]))
|
||||
|
||||
def delete_address(self, address):
|
||||
if address not in self.addresses:
|
||||
|
@ -1293,28 +1306,34 @@ class Imported_Wallet(Simple_Wallet):
|
|||
def get_public_key(self, address):
|
||||
return self.addresses[address].get('pubkey')
|
||||
|
||||
def import_private_key(self, sec, pw, redeem_script=None):
|
||||
try:
|
||||
txin_type, pubkey = self.keystore.import_privkey(sec, pw)
|
||||
except Exception:
|
||||
neutered_privkey = str(sec)[:3] + '..' + str(sec)[-2:]
|
||||
raise BitcoinException('Invalid private key: {}'.format(neutered_privkey))
|
||||
if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
|
||||
if redeem_script is not None:
|
||||
raise BitcoinException('Cannot use redeem script with script type {}'.format(txin_type))
|
||||
def import_private_keys(self, keys: List[str], password: Optional[str]) -> Tuple[List[str],
|
||||
List[Tuple[str, str]]]:
|
||||
good_addr = [] # type: List[str]
|
||||
bad_keys = [] # type: List[Tuple[str, str]]
|
||||
for key in keys:
|
||||
try:
|
||||
txin_type, pubkey = self.keystore.import_privkey(key, password)
|
||||
except Exception:
|
||||
bad_keys.append((key, _('invalid private key')))
|
||||
continue
|
||||
if txin_type not in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'):
|
||||
bad_keys.append((key, _('not implemented type') + f': {txin_type}'))
|
||||
continue
|
||||
addr = bitcoin.pubkey_to_address(txin_type, pubkey)
|
||||
elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
|
||||
if redeem_script is None:
|
||||
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)
|
||||
self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':redeem_script}
|
||||
good_addr.append(addr)
|
||||
self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':None}
|
||||
self.add_address(addr)
|
||||
self.save_keystore()
|
||||
self.add_address(addr)
|
||||
self.save_addresses()
|
||||
self.save_transactions(write=True)
|
||||
return addr
|
||||
return good_addr, bad_keys
|
||||
|
||||
def import_private_key(self, key: str, password: Optional[str]) -> str:
|
||||
good_addr, bad_keys = self.import_private_keys([key], password=password)
|
||||
if good_addr:
|
||||
return good_addr[0]
|
||||
else:
|
||||
raise BitcoinException(str(bad_keys[0][1]))
|
||||
|
||||
def get_redeem_script(self, address):
|
||||
d = self.addresses[address]
|
||||
|
|
Loading…
Add table
Reference in a new issue