Merge branch '1.9' of git://github.com/spesmilo/electrum into 1.9

This commit is contained in:
thomasv 2013-09-02 11:16:35 +02:00
commit 9d1c31255c
15 changed files with 736 additions and 638 deletions

View File

@ -107,7 +107,7 @@ if __name__ == '__main__':
util.check_windows_wallet_migration() util.check_windows_wallet_migration()
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
storage = WalletStorage(config)
if len(args)==0: if len(args)==0:
url = None url = None
@ -133,10 +133,14 @@ if __name__ == '__main__':
interface.start(wait = False) interface.start(wait = False)
interface.send([('server.peers.subscribe',[])]) interface.send([('server.peers.subscribe',[])])
gui = gui.ElectrumGui(config,interface) blockchain = BlockchainVerifier(interface, config)
blockchain.start()
gui = gui.ElectrumGui(config, interface, blockchain)
gui.main(url) gui.main(url)
interface.stop() interface.stop()
blockchain.stop()
# we use daemon threads, their termination is enforced. # we use daemon threads, their termination is enforced.
# this sleep command gives them time to terminate cleanly. # this sleep command gives them time to terminate cleanly.
@ -145,12 +149,12 @@ if __name__ == '__main__':
# instanciate wallet for command-line # instanciate wallet for command-line
wallet = Wallet(config) wallet = Wallet(storage)
if cmd not in known_commands: if cmd not in known_commands:
cmd = 'help' cmd = 'help'
if not config.wallet_file_exists and cmd not in ['help','create','restore']: if not storage.file_exists and cmd not in ['help','create','restore']:
print_msg("Error: Wallet file not found.") print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option") print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0) sys.exit(0)
@ -197,13 +201,11 @@ if __name__ == '__main__':
if not interface.start(wait=True): if not interface.start(wait=True):
print_msg("Not connected, aborting. Try option -o if you want to restore offline.") print_msg("Not connected, aborting. Try option -o if you want to restore offline.")
sys.exit(1) sys.exit(1)
wallet.interface = interface
verifier = WalletVerifier(interface, config)
verifier.start()
wallet.set_verifier(verifier)
blockchain = BlockchainVerifier(interface, config)
blockchain.start()
wallet.start_threads(interface, blockchain)
print_msg("Recovering wallet...") print_msg("Recovering wallet...")
WalletSynchronizer(wallet, config).start()
wallet.update() wallet.update()
if wallet.is_found(): if wallet.is_found():
print_msg("Recovery successful") print_msg("Recovery successful")
@ -317,26 +319,21 @@ if __name__ == '__main__':
message = ' '.join(args[min_args:]) message = ' '.join(args[min_args:])
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message)) print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
args = args[0:min_args] + [ message ] args = args[0:min_args] + [ message ]
# open session # open session
if cmd not in offline_commands and not options.offline: if cmd not in offline_commands and not options.offline:
interface = Interface(config) interface = Interface(config)
interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n")) interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
if not interface.start(wait=True): if not interface.start(wait=True):
print_msg("Not connected, aborting.") print_msg("Not connected, aborting.")
sys.exit(1) sys.exit(1)
wallet.interface = interface blockchain = BlockchainVerifier(interface, config)
verifier = WalletVerifier(interface, config) blockchain.start()
verifier.start() wallet.start_threads(interface, blockchain)
wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(wallet, config)
synchronizer.start()
wallet.update() wallet.update()
#wallet.save()
# run the command # run the command
@ -350,9 +347,9 @@ if __name__ == '__main__':
if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']: if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
wallet.config.path = ns wallet.config.path = ns
wallet.seed = '' wallet.seed = ''
wallet.config.set_key('seed', '', True) wallet.storage.put('seed', '', True)
wallet.use_encryption = False wallet.use_encryption = False
wallet.config.set_key('use_encryption', wallet.use_encryption, True) wallet.storage.put('use_encryption', wallet.use_encryption, True)
for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = '' for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
wallet.config.set_key('imported_keys',wallet.imported_keys, True) wallet.config.set_key('imported_keys',wallet.imported_keys, True)
print_msg("Done.") print_msg("Done.")
@ -361,12 +358,12 @@ if __name__ == '__main__':
elif cmd == 'getconfig': elif cmd == 'getconfig':
key = args[1] key = args[1]
print_msg(wallet.config.get(key)) print_msg(config.get(key))
elif cmd == 'setconfig': elif cmd == 'setconfig':
key, value = args[1:3] key, value = args[1:3]
if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']: if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
wallet.config.set_key(key, value, True) config.set_key(key, value, True)
print_msg(True) print_msg(True)
else: else:
print_msg(False) print_msg(False)
@ -394,8 +391,8 @@ if __name__ == '__main__':
if cmd not in offline_commands and not options.offline: if cmd not in offline_commands and not options.offline:
verifier.stop() wallet.stop_threads()
synchronizer.stop()
interface.stop() interface.stop()
blockchain.stop()
time.sleep(0.1) time.sleep(0.1)
sys.exit(0) sys.exit(0)

View File

@ -42,7 +42,9 @@ except:
from electrum.wallet import format_satoshis from electrum.wallet import format_satoshis
from electrum.bitcoin import Transaction, is_valid from electrum.bitcoin import Transaction, is_valid
from electrum import mnemonic from electrum import mnemonic
from electrum import util, bitcoin, commands, Interface, Wallet, WalletVerifier, WalletSynchronizer from electrum import util, bitcoin, commands, Interface, Wallet
from electrum import SimpleConfig, Wallet, WalletStorage
import bmp, pyqrnative import bmp, pyqrnative
import exchange_rate import exchange_rate
@ -225,9 +227,12 @@ class ElectrumWindow(QMainWindow):
def __init__(self, config): def __init__(self, config):
QMainWindow.__init__(self) QMainWindow.__init__(self)
self.config = config
self.init_plugins()
self._close_electrum = False self._close_electrum = False
self.lite = None self.lite = None
self.config = config
self.current_account = self.config.get("current_account", None) self.current_account = self.config.get("current_account", None)
self.icon = QIcon(os.getcwd() + '/icons/electrum.png') self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
@ -237,14 +242,13 @@ class ElectrumWindow(QMainWindow):
self.build_menu() self.build_menu()
self.tray.show() self.tray.show()
self.init_plugins()
self.create_status_bar() self.create_status_bar()
self.need_update = threading.Event() self.need_update = threading.Event()
self.expert_mode = config.get('classic_expert_mode', False) self.expert_mode = config.get('classic_expert_mode', False)
self.decimal_point = config.get('decimal_point', 8) self.decimal_point = config.get('decimal_point', 8)
self.num_zeros = int(config.get('num_zeros',0))
set_language(config.get('language')) set_language(config.get('language'))
@ -287,11 +291,12 @@ class ElectrumWindow(QMainWindow):
tabs.setCurrentIndex (n) tabs.setCurrentIndex (n)
tabs.setCurrentIndex (0) tabs.setCurrentIndex (0)
# plugins that need to change the GUI do it here # plugins that need to change the GUI do it here
self.run_hook('init') self.run_hook('init')
def load_wallet(self, wallet): def load_wallet(self, wallet):
import electrum import electrum
self.wallet = wallet self.wallet = wallet
@ -301,7 +306,7 @@ class ElectrumWindow(QMainWindow):
self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status'))) self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status'))) self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal'))) self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
if not self.wallet.seed: title += ' [%s]' % (_('seedless')) if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
self.setWindowTitle( title ) self.setWindowTitle( title )
self.update_wallet() self.update_wallet()
@ -312,59 +317,59 @@ class ElectrumWindow(QMainWindow):
# account selector # account selector
accounts = self.wallet.get_accounts() accounts = self.wallet.get_accounts()
self.account_selector.clear()
if len(accounts) > 1: if len(accounts) > 1:
self.account_selector.addItems([_("All accounts")] + accounts.values()) self.account_selector.addItems([_("All accounts")] + accounts.values())
self.account_selector.setCurrentIndex(0) self.account_selector.setCurrentIndex(0)
self.account_selector.show()
else:
self.account_selector.hide()
self.update_lock_icon()
self.update_buttons_on_seed()
self.update_console()
def select_wallet_file(self): def select_wallet_file(self):
wallet_folder = self.wallet.config.path wallet_folder = self.wallet.storage.path
re.sub("(\/\w*.dat)$", "", wallet_folder) re.sub("(\/\w*.dat)$", "", wallet_folder)
file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") ) file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
return file_name return file_name
def open_wallet(self): def open_wallet(self):
from electrum import SimpleConfig, Wallet, WalletSynchronizer
filename = self.select_wallet_file() filename = self.select_wallet_file()
if not filename: if not filename:
return return
config = SimpleConfig({'wallet_path': filename}) storage = WalletStorage({'wallet_path': filename})
if not config.wallet_file_exists: if not storage.file_exists:
self.show_message("file not found "+ filename) self.show_message("file not found "+ filename)
return return
interface = self.wallet.interface interface = self.wallet.interface
verifier = self.wallet.verifier blockchain = self.wallet.verifier.blockchain
self.wallet.synchronizer.stop() self.wallet.stop_threads()
self.config = config # create new wallet
wallet = Wallet(storage)
# create wallet wallet.start_threads(interface, blockchain)
wallet = Wallet(config)
wallet.interface = interface
wallet.verifier = verifier
synchronizer = WalletSynchronizer(wallet, config)
synchronizer.start()
self.load_wallet(wallet) self.load_wallet(wallet)
def new_wallet(self): def new_wallet(self):
from electrum import SimpleConfig, Wallet, WalletSynchronizer
import installwizard import installwizard
wallet_folder = self.wallet.config.path wallet_folder = self.wallet.storage.path
re.sub("(\/\w*.dat)$", "", wallet_folder) re.sub("(\/\w*.dat)$", "", wallet_folder)
filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat") filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
config = SimpleConfig({'wallet_path': filename}) storage = WalletStorage({'wallet_path': filename})
assert not config.wallet_file_exists assert not storage.file_exists
wizard = installwizard.InstallWizard(config, self.wallet.interface) wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
wallet = wizard.run() wallet = wizard.run()
if wallet: if wallet:
self.load_wallet(wallet) self.load_wallet(wallet)
@ -374,22 +379,29 @@ class ElectrumWindow(QMainWindow):
def init_menubar(self): def init_menubar(self):
menubar = QMenuBar() menubar = QMenuBar()
electrum_menu = menubar.addMenu(_("&File")) file_menu = menubar.addMenu(_("&File"))
open_wallet_action = electrum_menu.addAction(_("Open wallet")) open_wallet_action = file_menu.addAction(_("&Open"))
open_wallet_action.triggered.connect(self.open_wallet) open_wallet_action.triggered.connect(self.open_wallet)
new_wallet_action = electrum_menu.addAction(_("New wallet")) new_wallet_action = file_menu.addAction(_("&Create/Restore"))
new_wallet_action.triggered.connect(self.new_wallet) new_wallet_action.triggered.connect(self.new_wallet)
preferences_name = _("Preferences") wallet_backup = file_menu.addAction(_("&Copy"))
if sys.platform == 'darwin': wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
preferences_menu = electrum_menu.addAction(preferences_name) quit_item = file_menu.addAction(_("&Close"))
quit_item.triggered.connect(self.close)
wallet_menu = menubar.addMenu(_("&Wallet"))
# Settings / Preferences are all reserved keywords in OSX using this as work around
preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
preferences_menu = wallet_menu.addAction(preferences_name)
preferences_menu.triggered.connect(self.settings_dialog) preferences_menu.triggered.connect(self.settings_dialog)
electrum_menu.addSeparator()
raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction")) wallet_menu.addSeparator()
raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
raw_transaction_file = raw_transaction_menu.addAction(_("&From file")) raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
raw_transaction_file.triggered.connect(self.do_process_from_file) raw_transaction_file.triggered.connect(self.do_process_from_file)
@ -397,13 +409,7 @@ class ElectrumWindow(QMainWindow):
raw_transaction_text = raw_transaction_menu.addAction(_("&From text")) raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
raw_transaction_text.triggered.connect(self.do_process_from_text) raw_transaction_text.triggered.connect(self.do_process_from_text)
electrum_menu.addSeparator() wallet_menu.addSeparator()
quit_item = electrum_menu.addAction(_("&Close"))
quit_item.triggered.connect(self.close)
wallet_menu = menubar.addMenu(_("&Wallet"))
wallet_backup = wallet_menu.addAction(_("&Create backup"))
wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
show_menu = wallet_menu.addMenu(_("Show")) show_menu = wallet_menu.addMenu(_("Show"))
@ -514,14 +520,15 @@ class ElectrumWindow(QMainWindow):
return return
def set_label(self, name, text = None): def set_label(self, name, text = None):
changed = False changed = False
old_text = self.wallet.labels.get(name) old_text = self.wallet.labels.get(name)
if text: if text:
if old_text != text: if old_text != text:
self.wallet.labels[name] = text self.wallet.labels[name] = text
self.wallet.config.set_key('labels', self.wallet.labels) self.wallet.storage.put('labels', self.wallet.labels)
changed = True changed = True
else: else:
if old_text: if old_text:
@ -564,7 +571,7 @@ class ElectrumWindow(QMainWindow):
self.run_hook('timer_actions') self.run_hook('timer_actions')
def format_amount(self, x, is_diff=False, whitespaces=False): def format_amount(self, x, is_diff=False, whitespaces=False):
return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces) return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
def read_amount(self, x): def read_amount(self, x):
if x in['.', '']: return None if x in['.', '']: return None
@ -850,14 +857,10 @@ class ElectrumWindow(QMainWindow):
_('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\ _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
+ _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\ + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
+ _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3) + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
b = ''
if 1:#self.wallet.seed:
b = EnterButton(_("Send"), self.do_send)
else:
b = EnterButton(_("Create unsigned transaction"), self.do_send)
grid.addWidget(b, 6, 1) self.send_button = EnterButton(_("Send"), self.do_send)
grid.addWidget(self.send_button, 6, 1)
b = EnterButton(_("Clear"),self.do_clear) b = EnterButton(_("Clear"),self.do_clear)
grid.addWidget(b, 6, 2) grid.addWidget(b, 6, 2)
@ -1339,10 +1342,12 @@ class ElectrumWindow(QMainWindow):
from qt_console import Console from qt_console import Console
self.console = console = Console() self.console = console = Console()
return console return console
#
self.console.history = self.config.get("console-history",[])
self.console.history_index = len(self.console.history) def update_console(self):
console = self.console
console.history = self.config.get("console-history",[])
console.history_index = len(console.history)
console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self}) console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
console.updateNamespace({'util' : util, 'bitcoin':bitcoin}) console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
@ -1356,7 +1361,7 @@ class ElectrumWindow(QMainWindow):
methods[m] = mkfunc(c._run, m) methods[m] = mkfunc(c._run, m)
console.updateNamespace(methods) console.updateNamespace(methods)
return console
def change_account(self,s): def change_account(self,s):
if s == _("All accounts"): if s == _("All accounts"):
@ -1389,13 +1394,14 @@ class ElectrumWindow(QMainWindow):
if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7): if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) ) sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
if 1:#self.wallet.seed:
self.lock_icon = QIcon(":icons/lock.png") #if self.wallet.use_encryption else QIcon(":icons/unlock.png") self.lock_icon = QIcon()
self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog ) self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
sb.addPermanentWidget( self.password_button ) sb.addPermanentWidget( self.password_button )
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) ) sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
if 1:#self.wallet.seed: self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) ) sb.addPermanentWidget( self.seed_button )
self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
sb.addPermanentWidget( self.status_button ) sb.addPermanentWidget( self.status_button )
@ -1404,10 +1410,27 @@ class ElectrumWindow(QMainWindow):
self.setStatusBar(sb) self.setStatusBar(sb)
def update_lock_icon(self):
icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
self.password_button.setIcon( icon )
def update_buttons_on_seed(self):
if self.wallet.seed:
self.seed_button.show()
self.password_button.show()
self.send_button.setText(_("Send"))
else:
self.password_button.hide()
self.seed_button.hide()
self.send_button.setText(_("Create unsigned transaction"))
def change_password_dialog(self): def change_password_dialog(self):
from password_dialog import PasswordDialog from password_dialog import PasswordDialog
d = PasswordDialog(self.wallet, self) d = PasswordDialog(self.wallet, self)
d.run() d.run()
self.update_lock_icon()
def go_lite(self): def go_lite(self):
@ -1420,6 +1443,7 @@ class ElectrumWindow(QMainWindow):
self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self) self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
self.lite.main(None) self.lite.main(None)
def new_contact_dialog(self): def new_contact_dialog(self):
text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':') text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
address = unicode(text) address = unicode(text)
@ -1441,7 +1465,7 @@ class ElectrumWindow(QMainWindow):
addr = self.wallet.new_account_address() addr = self.wallet.new_account_address()
vbox = QVBoxLayout() vbox = QVBoxLayout()
vbox.addWidget(QLabel("To add another account, please send bitcoins to the following address:")) vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
e = QLineEdit(addr) e = QLineEdit(addr)
e.setReadOnly(True) e.setReadOnly(True)
vbox.addWidget(e) vbox.addWidget(e)
@ -1941,7 +1965,7 @@ class ElectrumWindow(QMainWindow):
nz_label = QLabel(_('Display zeros')) nz_label = QLabel(_('Display zeros'))
grid_ui.addWidget(nz_label, 0, 0) grid_ui.addWidget(nz_label, 0, 0)
nz_e = AmountEdit(None,True) nz_e = AmountEdit(None,True)
nz_e.setText("%d"% self.wallet.num_zeros) nz_e.setText("%d"% self.num_zeros)
grid_ui.addWidget(nz_e, 0, 1) grid_ui.addWidget(nz_e, 0, 1)
msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"') msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
grid_ui.addWidget(HelpButton(msg), 0, 2) grid_ui.addWidget(HelpButton(msg), 0, 2)
@ -2097,8 +2121,8 @@ class ElectrumWindow(QMainWindow):
QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK')) QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
return return
if self.wallet.num_zeros != nz: if self.num_zeros != nz:
self.wallet.num_zeros = nz self.num_zeros = nz
self.config.set_key('num_zeros', nz, True) self.config.set_key('num_zeros', nz, True)
self.update_history_tab() self.update_history_tab()
self.update_receive_tab() self.update_receive_tab()
@ -2155,7 +2179,7 @@ class ElectrumWindow(QMainWindow):
g = self.geometry() g = self.geometry()
self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True) self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
self.save_column_widths() self.save_column_widths()
self.config.set_key("console-history",self.console.history[-50:]) self.config.set_key("console-history", self.console.history[-50:], True)
event.accept() event.accept()
class OpenFileEventFilter(QObject): class OpenFileEventFilter(QObject):
@ -2175,9 +2199,10 @@ class OpenFileEventFilter(QObject):
class ElectrumGui: class ElectrumGui:
def __init__(self, config, interface, app=None): def __init__(self, config, interface, blockchain, app=None):
self.interface = interface self.interface = interface
self.config = config self.config = config
self.blockchain = blockchain
self.windows = [] self.windows = []
self.efilter = OpenFileEventFilter(self.windows) self.efilter = OpenFileEventFilter(self.windows)
if app is None: if app is None:
@ -2186,24 +2211,18 @@ class ElectrumGui:
def main(self, url): def main(self, url):
found = self.config.wallet_file_exists storage = WalletStorage(self.config)
if not found: if not storage.file_exists:
import installwizard import installwizard
wizard = installwizard.InstallWizard(self.config, self.interface) wizard = installwizard.InstallWizard(self.config, self.interface, self.blockchain, storage)
wallet = wizard.run() wallet = wizard.run()
if not wallet: if not wallet:
exit() exit()
else: else:
wallet = Wallet(self.config) wallet = Wallet(storage)
wallet.interface = self.interface wallet.start_threads(self.interface, self.blockchain)
verifier = WalletVerifier(self.interface, self.config)
verifier.start()
wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(wallet, self.config)
synchronizer.start()
s = Timer() s = Timer()
s.start() s.start()
@ -2219,7 +2238,6 @@ class ElectrumGui:
self.app.exec_() self.app.exec_()
verifier.stop() wallet.stop_threads()
synchronizer.stop()

View File

@ -5,12 +5,24 @@ _ = lambda x:x
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis, set_verbosity
from electrum.bitcoin import is_valid from electrum.bitcoin import is_valid
from electrum import Wallet, WalletStorage
import tty, sys import tty, sys
class ElectrumGui: class ElectrumGui:
def __init__(self, wallet, config, app=None): def __init__(self, config, interface, blockchain):
self.config = config
storage = WalletStorage(config)
if not storage.file_exists:
print "Wallet not found. try 'electrum create'"
exit()
self.wallet = Wallet(storage)
self.wallet.start_threads(interface, blockchain)
self.stdscr = curses.initscr() self.stdscr = curses.initscr()
curses.noecho() curses.noecho()
curses.cbreak() curses.cbreak()
@ -24,8 +36,6 @@ class ElectrumGui:
self.set_cursor(0) self.set_cursor(0)
self.w = curses.newwin(10, 50, 5, 5) self.w = curses.newwin(10, 50, 5, 5)
self.wallet = wallet
self.config = config
set_verbosity(False) set_verbosity(False)
self.tab = 0 self.tab = 0
self.pos = 0 self.pos = 0

View File

@ -3,7 +3,7 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore import PyQt4.QtCore as QtCore
from i18n import _ from i18n import _
from electrum import Wallet, mnemonic, WalletVerifier, WalletSynchronizer from electrum import Wallet, mnemonic
from seed_dialog import SeedDialog from seed_dialog import SeedDialog
from network_dialog import NetworkDialog from network_dialog import NetworkDialog
@ -14,10 +14,12 @@ import sys
class InstallWizard(QDialog): class InstallWizard(QDialog):
def __init__(self, config, interface): def __init__(self, config, interface, blockchain, storage):
QDialog.__init__(self) QDialog.__init__(self)
self.config = config self.config = config
self.interface = interface self.interface = interface
self.blockchain = blockchain
self.storage = storage
def restore_or_create(self): def restore_or_create(self):
@ -124,6 +126,15 @@ class InstallWizard(QDialog):
wallet.set_up_to_date(False) wallet.set_up_to_date(False)
wallet.interface.poke('synchronizer') wallet.interface.poke('synchronizer')
waiting_dialog(waiting) waiting_dialog(waiting)
# try to restore old account
if not wallet.is_found():
print "trying old method"
wallet.create_old_account()
wallet.set_up_to_date(False)
wallet.interface.poke('synchronizer')
waiting_dialog(waiting)
if wallet.is_found(): if wallet.is_found():
QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK')) QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
else: else:
@ -137,8 +148,7 @@ class InstallWizard(QDialog):
a = self.restore_or_create() a = self.restore_or_create()
if not a: exit() if not a: exit()
wallet = Wallet(self.config) wallet = Wallet(self.storage)
wallet.interface = self.interface
if a =='create': if a =='create':
wallet.init_seed(None) wallet.init_seed(None)
@ -170,11 +180,7 @@ class InstallWizard(QDialog):
#self.interface.start(wait = False) #self.interface.start(wait = False)
# start wallet threads # start wallet threads
verifier = WalletVerifier(self.interface, self.config) wallet.start_threads(self.interface, self.blockchain)
verifier.start()
wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(wallet, self.config)
synchronizer.start()
# generate the first addresses, in case we are offline # generate the first addresses, in case we are offline

View File

@ -44,7 +44,7 @@ class NetworkDialog(QDialog):
if parent: if parent:
if interface.is_connected: if interface.is_connected:
status = _("Connected to")+" %s"%(interface.host) + "\n%d "%(parent.wallet.verifier.height)+_("blocks") status = _("Connected to")+" %s"%(interface.host) + "\n%d "%(parent.wallet.verifier.blockchain.height)+_("blocks")
else: else:
status = _("Not connected") status = _("Not connected")
server = interface.server server = interface.server

View File

@ -96,9 +96,5 @@ class PasswordDialog(QDialog):
QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK')) QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK'))
if self.parent:
icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
self.parent.password_button.setIcon( icon )

View File

@ -1,8 +1,9 @@
from version import ELECTRUM_VERSION from version import ELECTRUM_VERSION
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
from wallet import WalletSynchronizer from wallet import WalletSynchronizer, WalletStorage
from wallet_factory import WalletFactory as Wallet from wallet_factory import WalletFactory as Wallet
from verifier import WalletVerifier from verifier import TxVerifier
from blockchain import BlockchainVerifier
from interface import Interface, pick_random_server, DEFAULT_SERVERS from interface import Interface, pick_random_server, DEFAULT_SERVERS
from simple_config import SimpleConfig from simple_config import SimpleConfig
import bitcoin import bitcoin

View File

@ -48,10 +48,13 @@ class Account(object):
class OldAccount(Account): class OldAccount(Account):
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """ """ Privatekey(type,n) = Master_private_key + H(n|S|type) """
def __init__(self, mpk, mpk2 = None, mpk3 = None): def __init__(self, v):
self.mpk = mpk self.addresses = v.get(0, [])
self.mpk2 = mpk2 self.change = v.get(1, [])
self.mpk3 = mpk3 self.mpk = v['mpk'].decode('hex')
def dump(self):
return {0:self.addresses, 1:self.change}
@classmethod @classmethod
def mpk_from_seed(klass, seed): def mpk_from_seed(klass, seed):
@ -68,48 +71,34 @@ class OldAccount(Account):
seed = hashlib.sha256(seed + oldseed).digest() seed = hashlib.sha256(seed + oldseed).digest()
return string_to_number( seed ) return string_to_number( seed )
def get_sequence(self, sequence, mpk): def get_sequence(self, for_change, n):
for_change, n = sequence return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.mpk ) )
return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk.decode('hex') ) )
def get_address(self, sequence): def get_address(self, for_change, n):
if not self.mpk2: pubkey = self.get_pubkey(for_change, n)
pubkey = self.get_pubkey(sequence) address = public_key_to_bc_address( pubkey.decode('hex') )
address = public_key_to_bc_address( pubkey.decode('hex') )
elif not self.mpk3:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
else:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3)
address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"]
return address return address
def get_pubkey(self, sequence, mpk=None): def get_pubkey(self, for_change, n):
curve = SECP256k1 curve = SECP256k1
if mpk is None: mpk = self.mpk mpk = self.mpk
z = self.get_sequence(sequence, mpk) z = self.get_sequence(for_change, n)
master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 ) master_public_key = ecdsa.VerifyingKey.from_string( mpk, curve = SECP256k1 )
pubkey_point = master_public_key.pubkey.point + z*curve.generator pubkey_point = master_public_key.pubkey.point + z*curve.generator
public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 ) public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
return '04' + public_key2.to_string().encode('hex') return '04' + public_key2.to_string().encode('hex')
def get_private_key_from_stretched_exponent(self, sequence, secexp): def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
order = generator_secp256k1.order() order = generator_secp256k1.order()
secexp = ( secexp + self.get_sequence(sequence, self.mpk) ) % order secexp = ( secexp + self.get_sequence(for_change, n) ) % order
pk = number_to_string( secexp, generator_secp256k1.order() ) pk = number_to_string( secexp, generator_secp256k1.order() )
compressed = False compressed = False
return SecretToASecret( pk, compressed ) return SecretToASecret( pk, compressed )
def get_private_key(self, sequence, seed): def get_private_key(self, seed, sequence):
for_change, n = sequence
secexp = self.stretch_key(seed) secexp = self.stretch_key(seed)
return self.get_private_key_from_stretched_exponent(sequence, secexp) return self.get_private_key_from_stretched_exponent(for_change, n, secexp)
def get_private_keys(self, sequence_list, seed):
secexp = self.stretch_key(seed)
return [ self.get_private_key_from_stretched_exponent( sequence, secexp) for sequence in sequence_list]
def check_seed(self, seed): def check_seed(self, seed):
curve = SECP256k1 curve = SECP256k1
@ -121,23 +110,8 @@ class OldAccount(Account):
raise BaseException('Invalid password') raise BaseException('Invalid password')
return True return True
def get_input_info(self, sequence): def redeem_script(self, sequence):
if not self.mpk2: return None
pk_addr = self.get_address(sequence)
redeemScript = None
elif not self.mpk3:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2)
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
else:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
return pk_addr, redeemScript
class BIP32_Account(Account): class BIP32_Account(Account):

329
lib/blockchain.py Normal file
View File

@ -0,0 +1,329 @@
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2012 thomasv@ecdsa.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import threading, time, Queue, os, sys, shutil
from util import user_dir, appdata_dir, print_error
from bitcoin import *
class BlockchainVerifier(threading.Thread):
""" Simple Payment Verification """
def __init__(self, interface, config):
threading.Thread.__init__(self)
self.daemon = True
self.config = config
self.interface = interface
self.interface.register_channel('verifier')
self.lock = threading.Lock()
self.pending_headers = [] # headers that have not been verified
self.height = 0
self.local_height = 0
self.running = False
self.headers_url = 'http://headers.electrum.org/blockchain_headers'
def stop(self):
with self.lock: self.running = False
self.interface.poke('verifier')
def is_running(self):
with self.lock: return self.running
def run(self):
self.init_headers_file()
self.set_local_height()
with self.lock:
self.running = True
requested_chunks = []
requested_headers = []
all_chunks = False
# subscribe to block headers
self.interface.send([ ('blockchain.headers.subscribe',[])], 'verifier')
while self.is_running():
# request missing chunks
if not all_chunks and self.height and not requested_chunks:
if self.local_height + 50 < self.height:
min_index = (self.local_height + 1)/2016
max_index = (self.height + 1)/2016
for i in range(min_index, max_index + 1):
print_error( "requesting chunk", i )
self.interface.send([ ('blockchain.block.get_chunk',[i])], 'verifier')
requested_chunks.append(i)
break
else:
all_chunks = True
print_error("downloaded all chunks")
# process pending headers
if self.pending_headers and all_chunks:
done = []
for header in self.pending_headers:
if self.verify_header(header):
done.append(header)
else:
# request previous header
i = header.get('block_height') - 1
if i not in requested_headers:
print_error("requesting header %d"%i)
self.interface.send([ ('blockchain.block.get_header',[i])], 'verifier')
requested_headers.append(i)
# no point continuing
break
if done:
self.interface.trigger_callback('updated')
for header in done:
self.pending_headers.remove(header)
try:
r = self.interface.get_response('verifier',timeout=1)
except Queue.Empty:
continue
if not r: continue
if r.get('error'):
print_error('Verifier received an error:', r)
continue
# 3. handle response
method = r['method']
params = r['params']
result = r['result']
if method == 'blockchain.block.get_chunk':
index = params[0]
self.verify_chunk(index, result)
requested_chunks.remove(index)
elif method in ['blockchain.headers.subscribe', 'blockchain.block.get_header']:
self.pending_headers.append(result)
if method == 'blockchain.block.get_header':
requested_headers.remove(result.get('block_height'))
else:
self.height = result.get('block_height')
## fixme # self.interface.poke('synchronizer')
self.pending_headers.sort(key=lambda x: x.get('block_height'))
# print "pending headers", map(lambda x: x.get('block_height'), self.pending_headers)
def verify_chunk(self, index, hexdata):
data = hexdata.decode('hex')
height = index*2016
num = len(data)/80
print_error("validating headers %d"%height)
if index == 0:
previous_hash = ("0"*64)
else:
prev_header = self.read_header(index*2016-1)
if prev_header is None: raise
previous_hash = self.hash_header(prev_header)
bits, target = self.get_target(index)
for i in range(num):
height = index*2016 + i
raw_header = data[i*80:(i+1)*80]
header = self.header_from_string(raw_header)
_hash = self.hash_header(header)
assert previous_hash == header.get('prev_block_hash')
assert bits == header.get('bits')
assert eval('0x'+_hash) < target
previous_header = header
previous_hash = _hash
self.save_chunk(index, data)
def verify_header(self, header):
# add header to the blockchain file
# if there is a reorg, push it in a stack
height = header.get('block_height')
prev_header = self.read_header(height -1)
if not prev_header:
# return False to request previous header
return False
prev_hash = self.hash_header(prev_header)
bits, target = self.get_target(height/2016)
_hash = self.hash_header(header)
try:
assert prev_hash == header.get('prev_block_hash')
assert bits == header.get('bits')
assert eval('0x'+_hash) < target
except:
# this can be caused by a reorg.
print_error("verify header failed"+ repr(header))
# undo verifications
with self.lock:
items = self.verified_tx.items()[:]
for tx_hash, item in items:
tx_height, timestamp, pos = item
if tx_height >= height:
print_error("redoing", tx_hash)
with self.lock:
self.verified_tx.pop(tx_hash)
if tx_hash in self.merkle_roots:
self.merkle_roots.pop(tx_hash)
# return False to request previous header.
return False
self.save_header(header)
print_error("verify header:", _hash, height)
return True
def header_to_string(self, res):
s = int_to_hex(res.get('version'),4) \
+ rev_hex(res.get('prev_block_hash')) \
+ rev_hex(res.get('merkle_root')) \
+ int_to_hex(int(res.get('timestamp')),4) \
+ int_to_hex(int(res.get('bits')),4) \
+ int_to_hex(int(res.get('nonce')),4)
return s
def header_from_string(self, s):
hex_to_int = lambda s: eval('0x' + s[::-1].encode('hex'))
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['timestamp'] = hex_to_int(s[68:72])
h['bits'] = hex_to_int(s[72:76])
h['nonce'] = hex_to_int(s[76:80])
return h
def hash_header(self, header):
return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
def path(self):
wdir = self.config.get('blockchain_headers_path', user_dir())
if wdir and not os.path.exists( wdir ): os.mkdir(wdir)
return os.path.join( wdir, 'blockchain_headers')
def init_headers_file(self):
filename = self.path()
if os.path.exists(filename):
return
try:
import urllib, socket
socket.setdefaulttimeout(30)
print_error("downloading ", self.headers_url )
urllib.urlretrieve(self.headers_url, filename)
except:
print_error( "download failed. creating file", filename )
open(filename,'wb+').close()
def save_chunk(self, index, chunk):
filename = self.path()
f = open(filename,'rb+')
f.seek(index*2016*80)
h = f.write(chunk)
f.close()
self.set_local_height()
def save_header(self, header):
data = self.header_to_string(header).decode('hex')
assert len(data) == 80
height = header.get('block_height')
filename = self.path()
f = open(filename,'rb+')
f.seek(height*80)
h = f.write(data)
f.close()
self.set_local_height()
def set_local_height(self):
name = self.path()
if os.path.exists(name):
h = os.path.getsize(name)/80 - 1
if self.local_height != h:
self.local_height = h
def read_header(self, block_height):
name = self.path()
if os.path.exists(name):
f = open(name,'rb')
f.seek(block_height*80)
h = f.read(80)
f.close()
if len(h) == 80:
h = self.header_from_string(h)
return h
def get_target(self, index):
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
if index == 0: return 0x1d00ffff, max_target
first = self.read_header((index-1)*2016)
last = self.read_header(index*2016-1)
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14*24*60*60
nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
bits = last.get('bits')
# convert to bignum
MM = 256*256*256
a = bits%MM
if a < 0x8000:
a *= 256
target = (a) * pow(2, 8 * (bits/MM - 3))
# new target
new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
# convert it to bits
c = ("%064X"%new_target)[2:]
i = 31
while c[0:2]=="00":
c = c[2:]
i -= 1
c = eval('0x'+c[0:6])
if c > 0x800000:
c /= 256
i += 1
new_bits = c + MM * i
return new_bits, new_target

View File

@ -396,7 +396,7 @@ class Interface(threading.Thread):
self.unanswered_requests[self.message_id] = method, params, channel self.unanswered_requests[self.message_id] = method, params, channel
ids.append(self.message_id) ids.append(self.message_id)
# uncomment to debug # uncomment to debug
# print "-->",request # print "-->", request
self.message_id += 1 self.message_id += 1
out += request + '\n' out += request + '\n'
while out: while out:

View File

@ -6,6 +6,9 @@ from version import ELECTRUM_VERSION, SEED_VERSION
class SimpleConfig: class SimpleConfig:
""" """
The SimpleConfig class is responsible for handling operations involving The SimpleConfig class is responsible for handling operations involving
@ -29,46 +32,44 @@ a SimpleConfig instance then reads the wallet file.
# command-line options # command-line options
self.options_config = options self.options_config = options
self.wallet_config = {} # init path
self.wallet_file_exists = False self.init_path(options)
self.init_path(self.options_config.get('wallet_path'))
print_error( "path", self.path ) print_error( "user dir", self.user_dir)
if self.path:
self.read_wallet_config(self.path)
def init_path(self, options):
# Look for wallet file in the default data directory.
# Make wallet directory if it does not yet exist.
if not os.path.exists(self.user_dir):
os.mkdir(self.user_dir)
# portable wallet: use the same directory for wallet and headers file # portable wallet: use the same directory for wallet and headers file
if options.get('portable'): #if options.get('portable'):
self.wallet_config['blockchain_headers_path'] = os.path.dirname(self.path) # self.wallet_config['blockchain_headers_path'] = os.path.dirname(self.path)
def set_key(self, key, value, save = True):
def set_key(self, key, value, save = False):
# find where a setting comes from and save it there # find where a setting comes from and save it there
if self.options_config.get(key) is not None: if self.options_config.get(key) is not None:
print "Warning: not changing '%s' because it was passed as a command-line option"%key print "Warning: not changing '%s' because it was passed as a command-line option"%key
return return
elif self.user_config.get(key) is not None:
self.user_config[key] = value
if save: self.save_user_config()
elif self.system_config.get(key) is not None: elif self.system_config.get(key) is not None:
if str(self.system_config[key]) != str(value): if str(self.system_config[key]) != str(value):
print "Warning: not changing '%s' because it was set in the system configuration"%key print "Warning: not changing '%s' because it was set in the system configuration"%key
elif self.wallet_config.get(key) is not None:
self.wallet_config[key] = value
if save: self.save_wallet_config()
else: else:
# add key to wallet config self.user_config[key] = value
self.wallet_config[key] = value if save: self.save_user_config()
if save: self.save_wallet_config()
def get(self, key, default=None): def get(self, key, default=None):
"""Retrieve the filepath of the configuration file specified in the 'key' parameter."""
out = None
# 1. command-line options always override everything # 1. command-line options always override everything
if self.options_config.has_key(key) and self.options_config.get(key) is not None: if self.options_config.has_key(key) and self.options_config.get(key) is not None:
out = self.options_config.get(key) out = self.options_config.get(key)
@ -81,10 +82,6 @@ a SimpleConfig instance then reads the wallet file.
elif self.system_config.has_key(key): elif self.system_config.has_key(key):
out = self.system_config.get(key) out = self.system_config.get(key)
# 3. use the wallet file config
else:
out = self.wallet_config.get(key)
if out is None and default is not None: if out is None and default is not None:
out = default out = default
@ -135,85 +132,29 @@ a SimpleConfig instance then reads the wallet file.
"""Parse and store the user config settings in electrum.conf into user_config[].""" """Parse and store the user config settings in electrum.conf into user_config[]."""
if not self.user_dir: return if not self.user_dir: return
name = os.path.join( self.user_dir, 'electrum.conf') path = os.path.join(self.user_dir, "config")
if os.path.exists(name): if os.path.exists(path):
try: try:
import ConfigParser with open(path, "r") as f:
except ImportError: data = f.read()
print "cannot parse electrum.conf. please install ConfigParser" except IOError:
return return
p = ConfigParser.ConfigParser()
p.read(name)
try: try:
for k, v in p.items('client'): d = ast.literal_eval( data ) #parse raw data from reading wallet file
self.user_config[k] = v except:
except ConfigParser.NoSectionError: raise IOError("Cannot read config file.")
pass
def init_path(self, path): self.user_config = d
"""Set the path of the wallet."""
if not path:
path = self.get('default_wallet_path')
if path is not None:
self.path = path
return
# Look for wallet file in the default data directory.
# Make wallet directory if it does not yet exist.
if not os.path.exists(self.user_dir):
os.mkdir(self.user_dir)
self.path = os.path.join(self.user_dir, "electrum.dat")
def save_user_config(self): def save_user_config(self):
if not self.user_dir: return if not self.user_dir: return
import ConfigParser path = os.path.join(self.user_dir, "config")
config = ConfigParser.RawConfigParser() s = repr(self.user_config)
config.add_section('client') f = open(path,"w")
for k,v in self.user_config.items():
config.set('client', k, v)
with open( os.path.join( self.user_dir, 'electrum.conf'), 'wb') as configfile:
config.write(configfile)
def read_wallet_config(self, path):
"""Read the contents of the wallet file."""
try:
with open(self.path, "r") as f:
data = f.read()
except IOError:
return
try:
d = ast.literal_eval( data ) #parse raw data from reading wallet file
except:
raise IOError("Cannot read wallet file.")
self.wallet_config = d
self.wallet_file_exists = True
def save(self, key=None):
self.save_wallet_config()
def save_wallet_config(self):
# prevent the creation of incomplete wallets
if self.wallet_config.get('master_public_keys') is None:
return
s = repr(self.wallet_config)
f = open(self.path,"w")
f.write( s ) f.write( s )
f.close() f.close()
if self.get('gui') != 'android': if self.get('gui') != 'android':
import stat import stat
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE) os.chmod(path, stat.S_IREAD | stat.S_IWRITE)

View File

@ -24,34 +24,29 @@ from bitcoin import *
class WalletVerifier(threading.Thread): class TxVerifier(threading.Thread):
""" Simple Payment Verification """ """ Simple Payment Verification """
def __init__(self, interface, config): def __init__(self, interface, blockchain, storage):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.daemon = True self.daemon = True
self.config = config self.storage = storage
self.blockchain = blockchain
self.interface = interface self.interface = interface
self.transactions = {} # requested verifications (with height sent by the requestor) self.transactions = {} # requested verifications (with height sent by the requestor)
self.interface.register_channel('verifier') self.interface.register_channel('txverifier')
self.verified_tx = storage.get('verified_tx3',{}) # height, timestamp of verified transactions
self.verified_tx = config.get('verified_tx3',{}) # height, timestamp of verified transactions self.merkle_roots = storage.get('merkle_roots',{}) # hashed by me
self.merkle_roots = config.get('merkle_roots',{}) # hashed by me
self.targets = config.get('targets',{}) # compute targets
self.lock = threading.Lock() self.lock = threading.Lock()
self.pending_headers = [] # headers that have not been verified
self.height = 0
self.local_height = 0
self.running = False self.running = False
self.headers_url = 'http://headers.electrum.org/blockchain_headers'
def get_confirmations(self, tx): def get_confirmations(self, tx):
""" return the number of confirmations of a monitored transaction. """ """ return the number of confirmations of a monitored transaction. """
with self.lock: with self.lock:
if tx in self.verified_tx: if tx in self.verified_tx:
height, timestamp, pos = self.verified_tx[tx] height, timestamp, pos = self.verified_tx[tx]
conf = (self.local_height - height + 1) conf = (self.blockchain.local_height - height + 1)
if conf <= 0: timestamp = None if conf <= 0: timestamp = None
elif tx in self.transactions: elif tx in self.transactions:
@ -101,67 +96,21 @@ class WalletVerifier(threading.Thread):
with self.lock: return self.running with self.lock: return self.running
def run(self): def run(self):
self.init_headers_file()
self.set_local_height()
with self.lock: with self.lock:
self.running = True self.running = True
requested_merkle = [] requested_merkle = []
requested_chunks = []
requested_headers = []
all_chunks = False
# subscribe to block headers
self.interface.send([ ('blockchain.headers.subscribe',[])], 'verifier')
while self.is_running(): while self.is_running():
# request missing chunks
if not all_chunks and self.height and not requested_chunks:
if self.local_height + 50 < self.height:
min_index = (self.local_height + 1)/2016
max_index = (self.height + 1)/2016
for i in range(min_index, max_index + 1):
print_error( "requesting chunk", i )
self.interface.send([ ('blockchain.block.get_chunk',[i])], 'verifier')
requested_chunks.append(i)
break
else:
all_chunks = True
print_error("downloaded all chunks")
# request missing tx # request missing tx
if all_chunks: for tx_hash, tx_height in self.transactions.items():
for tx_hash, tx_height in self.transactions.items(): if tx_hash not in self.verified_tx:
if tx_hash not in self.verified_tx: if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle:
if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle: print_error('requesting merkle', tx_hash)
print_error('requesting merkle', tx_hash) self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], 'txverifier')
self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], 'verifier') requested_merkle.append(tx_hash)
requested_merkle.append(tx_hash)
# process pending headers
if self.pending_headers and all_chunks:
done = []
for header in self.pending_headers:
if self.verify_header(header):
done.append(header)
else:
# request previous header
i = header.get('block_height') - 1
if i not in requested_headers:
print_error("requesting header %d"%i)
self.interface.send([ ('blockchain.block.get_header',[i])], 'verifier')
requested_headers.append(i)
# no point continuing
break
if done:
self.interface.trigger_callback('updated')
for header in done:
self.pending_headers.remove(header)
try: try:
r = self.interface.get_response('verifier',timeout=1) r = self.interface.get_response('txverifier',timeout=1)
except Queue.Empty: except Queue.Empty:
continue continue
if not r: continue if not r: continue
@ -180,138 +129,23 @@ class WalletVerifier(threading.Thread):
self.verify_merkle(tx_hash, result) self.verify_merkle(tx_hash, result)
requested_merkle.remove(tx_hash) requested_merkle.remove(tx_hash)
elif method == 'blockchain.block.get_chunk':
index = params[0]
self.verify_chunk(index, result)
requested_chunks.remove(index)
elif method in ['blockchain.headers.subscribe', 'blockchain.block.get_header']:
self.pending_headers.append(result)
if method == 'blockchain.block.get_header':
requested_headers.remove(result.get('block_height'))
else:
self.height = result.get('block_height')
self.interface.poke('synchronizer')
self.pending_headers.sort(key=lambda x: x.get('block_height'))
# print "pending headers", map(lambda x: x.get('block_height'), self.pending_headers)
def verify_merkle(self, tx_hash, result): def verify_merkle(self, tx_hash, result):
tx_height = result.get('block_height') tx_height = result.get('block_height')
pos = result.get('pos') pos = result.get('pos')
self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash, pos) self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash, pos)
header = self.read_header(tx_height) header = self.blockchain.read_header(tx_height)
if not header: return if not header: return
assert header.get('merkle_root') == self.merkle_roots[tx_hash] assert header.get('merkle_root') == self.merkle_roots[tx_hash]
# we passed all the tests # we passed all the tests
header = self.read_header(tx_height)
timestamp = header.get('timestamp') timestamp = header.get('timestamp')
with self.lock: with self.lock:
self.verified_tx[tx_hash] = (tx_height, timestamp, pos) self.verified_tx[tx_hash] = (tx_height, timestamp, pos)
print_error("verified %s"%tx_hash) print_error("verified %s"%tx_hash)
self.config.set_key('verified_tx3', self.verified_tx, True) self.storage.put('verified_tx3', self.verified_tx, True)
self.interface.trigger_callback('updated') self.interface.trigger_callback('updated')
def verify_chunk(self, index, hexdata):
data = hexdata.decode('hex')
height = index*2016
num = len(data)/80
print_error("validating headers %d"%height)
if index == 0:
previous_hash = ("0"*64)
else:
prev_header = self.read_header(index*2016-1)
if prev_header is None: raise
previous_hash = self.hash_header(prev_header)
bits, target = self.get_target(index)
for i in range(num):
height = index*2016 + i
raw_header = data[i*80:(i+1)*80]
header = self.header_from_string(raw_header)
_hash = self.hash_header(header)
assert previous_hash == header.get('prev_block_hash')
assert bits == header.get('bits')
assert eval('0x'+_hash) < target
previous_header = header
previous_hash = _hash
self.save_chunk(index, data)
def verify_header(self, header):
# add header to the blockchain file
# if there is a reorg, push it in a stack
height = header.get('block_height')
prev_header = self.read_header(height -1)
if not prev_header:
# return False to request previous header
return False
prev_hash = self.hash_header(prev_header)
bits, target = self.get_target(height/2016)
_hash = self.hash_header(header)
try:
assert prev_hash == header.get('prev_block_hash')
assert bits == header.get('bits')
assert eval('0x'+_hash) < target
except:
# this can be caused by a reorg.
print_error("verify header failed"+ repr(header))
# undo verifications
with self.lock:
items = self.verified_tx.items()[:]
for tx_hash, item in items:
tx_height, timestamp, pos = item
if tx_height >= height:
print_error("redoing", tx_hash)
with self.lock:
self.verified_tx.pop(tx_hash)
if tx_hash in self.merkle_roots:
self.merkle_roots.pop(tx_hash)
# return False to request previous header.
return False
self.save_header(header)
print_error("verify header:", _hash, height)
return True
def header_to_string(self, res):
s = int_to_hex(res.get('version'),4) \
+ rev_hex(res.get('prev_block_hash')) \
+ rev_hex(res.get('merkle_root')) \
+ int_to_hex(int(res.get('timestamp')),4) \
+ int_to_hex(int(res.get('bits')),4) \
+ int_to_hex(int(res.get('nonce')),4)
return s
def header_from_string(self, s):
hex_to_int = lambda s: eval('0x' + s[::-1].encode('hex'))
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['timestamp'] = hex_to_int(s[68:72])
h['bits'] = hex_to_int(s[72:76])
h['nonce'] = hex_to_int(s[76:80])
return h
def hash_header(self, header):
return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
def hash_merkle_root(self, merkle_s, target_hash, pos): def hash_merkle_root(self, merkle_s, target_hash, pos):
h = hash_decode(target_hash) h = hash_decode(target_hash)
for i in range(len(merkle_s)): for i in range(len(merkle_s)):
@ -319,101 +153,6 @@ class WalletVerifier(threading.Thread):
h = Hash( hash_decode(item) + h ) if ((pos >> i) & 1) else Hash( h + hash_decode(item) ) h = Hash( hash_decode(item) + h ) if ((pos >> i) & 1) else Hash( h + hash_decode(item) )
return hash_encode(h) return hash_encode(h)
def path(self):
wdir = self.config.get('blockchain_headers_path', user_dir())
if wdir and not os.path.exists( wdir ): os.mkdir(wdir)
return os.path.join( wdir, 'blockchain_headers')
def init_headers_file(self):
filename = self.path()
if os.path.exists(filename):
return
try:
import urllib, socket
socket.setdefaulttimeout(30)
print_error("downloading ", self.headers_url )
urllib.urlretrieve(self.headers_url, filename)
except:
print_error( "download failed. creating file", filename )
open(filename,'wb+').close()
def save_chunk(self, index, chunk):
filename = self.path()
f = open(filename,'rb+')
f.seek(index*2016*80)
h = f.write(chunk)
f.close()
self.set_local_height()
def save_header(self, header):
data = self.header_to_string(header).decode('hex')
assert len(data) == 80
height = header.get('block_height')
filename = self.path()
f = open(filename,'rb+')
f.seek(height*80)
h = f.write(data)
f.close()
self.set_local_height()
def set_local_height(self):
name = self.path()
if os.path.exists(name):
h = os.path.getsize(name)/80 - 1
if self.local_height != h:
self.local_height = h
def read_header(self, block_height):
name = self.path()
if os.path.exists(name):
f = open(name,'rb')
f.seek(block_height*80)
h = f.read(80)
f.close()
if len(h) == 80:
h = self.header_from_string(h)
return h
def get_target(self, index):
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
if index == 0: return 0x1d00ffff, max_target
first = self.read_header((index-1)*2016)
last = self.read_header(index*2016-1)
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14*24*60*60
nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
bits = last.get('bits')
# convert to bignum
MM = 256*256*256
a = bits%MM
if a < 0x8000:
a *= 256
target = (a) * pow(2, 8 * (bits/MM - 3))
# new target
new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
# convert it to bits
c = ("%064X"%new_target)[2:]
i = 31
while c[0:2]=="00":
c = c[2:]
i -= 1
c = eval('0x'+c[0:6])
if c > 0x800000:
c /= 256
i += 1
new_bits = c + MM * i
return new_bits, new_target

View File

@ -1,4 +1,4 @@
ELECTRUM_VERSION = "1.9" # version of the client package ELECTRUM_VERSION = "1.9" # version of the client package
PROTOCOL_VERSION = '0.6' # protocol version requested PROTOCOL_VERSION = '0.6' # protocol version requested
SEED_VERSION = 4 # bump this every time the seed generation is modified SEED_VERSION = 5 # bump this every time the seed generation is modified
TRANSLATION_ID = 4101 # version of the wiki page TRANSLATION_ID = 4101 # version of the wiki page

View File

@ -63,39 +63,113 @@ def pw_decode(s, password):
from version import ELECTRUM_VERSION, SEED_VERSION from version import ELECTRUM_VERSION, SEED_VERSION
class Wallet: class WalletStorage:
def __init__(self, config={}):
self.config = config def __init__(self, config):
self.data = {}
self.file_exists = False
self.init_path(config)
print_error( "wallet path", self.path )
if self.path:
self.read(self.path)
def init_path(self, config):
"""Set the path of the wallet."""
path = config.get('wallet_path')
if not path:
path = config.get('default_wallet_path')
if path is not None:
self.path = path
return
# Look for wallet file in the default data directory.
# Make wallet directory if it does not yet exist.
if not os.path.exists(self.user_dir):
os.mkdir(self.user_dir)
self.path = os.path.join(self.user_dir, "electrum.dat")
def read(self, path):
"""Read the contents of the wallet file."""
try:
with open(self.path, "r") as f:
data = f.read()
except IOError:
return
try:
d = ast.literal_eval( data ) #parse raw data from reading wallet file
except:
raise IOError("Cannot read wallet file.")
self.data = d
self.file_exists = True
def get(self, key, default=None):
return self.data.get(key, default)
def put(self, key, value, save = True):
if self.data.get(key) is not None:
self.data[key] = value
else:
# add key to wallet config
self.data[key] = value
if save:
self.write()
def write(self):
s = repr(self.data)
f = open(self.path,"w")
f.write( s )
f.close()
if self.get('gui') != 'android':
import stat
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
class Wallet:
def __init__(self, storage):
self.storage = storage
self.electrum_version = ELECTRUM_VERSION self.electrum_version = ELECTRUM_VERSION
self.gap_limit_for_change = 3 # constant self.gap_limit_for_change = 3 # constant
# saved fields # saved fields
self.seed_version = config.get('seed_version', SEED_VERSION) self.seed_version = storage.get('seed_version', SEED_VERSION)
self.gap_limit = config.get('gap_limit', 5)
self.use_change = config.get('use_change',True)
self.fee = int(config.get('fee_per_kb',20000))
self.num_zeros = int(config.get('num_zeros',0))
self.use_encryption = config.get('use_encryption', False)
self.seed = config.get('seed', '') # encrypted
self.labels = config.get('labels', {})
self.frozen_addresses = config.get('frozen_addresses',[])
self.prioritized_addresses = config.get('prioritized_addresses',[])
self.addressbook = config.get('contacts', [])
self.imported_keys = config.get('imported_keys',{}) self.gap_limit = storage.get('gap_limit', 5)
self.history = config.get('addr_history',{}) # address -> list(txid, height) self.use_change = storage.get('use_change',True)
self.use_encryption = storage.get('use_encryption', False)
self.seed = storage.get('seed', '') # encrypted
self.labels = storage.get('labels', {})
self.frozen_addresses = storage.get('frozen_addresses',[])
self.prioritized_addresses = storage.get('prioritized_addresses',[])
self.addressbook = storage.get('contacts', [])
self.imported_keys = storage.get('imported_keys',{})
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
self.master_public_keys = config.get('master_public_keys',{}) self.fee = int(storage.get('fee_per_kb',20000))
self.master_private_keys = config.get('master_private_keys', {})
self.first_addresses = config.get('first_addresses',{}) self.master_public_keys = storage.get('master_public_keys',{})
self.master_private_keys = storage.get('master_private_keys', {})
self.load_accounts(config) self.first_addresses = storage.get('first_addresses',{})
#if self.seed_version != SEED_VERSION:
# raise ValueError("This wallet seed is deprecated. Please restore from seed.")
self.load_accounts()
self.transactions = {} self.transactions = {}
tx = config.get('transactions',{}) tx = storage.get('transactions',{})
try: try:
for k,v in tx.items(): self.transactions[k] = Transaction(v) for k,v in tx.items(): self.transactions[k] = Transaction(v)
except: except:
@ -117,9 +191,6 @@ class Wallet:
self.transaction_lock = threading.Lock() self.transaction_lock = threading.Lock()
self.tx_event = threading.Event() self.tx_event = threading.Event()
if self.seed_version != SEED_VERSION:
raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
for tx_hash, tx in self.transactions.items(): for tx_hash, tx in self.transactions.items():
if self.check_new_tx(tx_hash, tx): if self.check_new_tx(tx_hash, tx):
self.update_tx_outputs(tx_hash) self.update_tx_outputs(tx_hash)
@ -152,13 +223,13 @@ class Wallet:
# store the originally requested keypair into the imported keys table # store the originally requested keypair into the imported keys table
self.imported_keys[address] = pw_encode(sec, password ) self.imported_keys[address] = pw_encode(sec, password )
self.config.set_key('imported_keys', self.imported_keys, True) self.storage.put('imported_keys', self.imported_keys, True)
return address return address
def delete_imported_key(self, addr): def delete_imported_key(self, addr):
if addr in self.imported_keys: if addr in self.imported_keys:
self.imported_keys.pop(addr) self.imported_keys.pop(addr)
self.config.set_key('imported_keys', self.imported_keys, True) self.storage.put('imported_keys', self.imported_keys, True)
def init_seed(self, seed): def init_seed(self, seed):
@ -169,8 +240,8 @@ class Wallet:
def save_seed(self): def save_seed(self):
self.config.set_key('seed', self.seed, True) self.storage.put('seed', self.seed, True)
self.config.set_key('seed_version', self.seed_version, True) self.storage.put('seed_version', self.seed_version, True)
master_k, master_c, master_K, master_cK = bip32_init(self.seed) master_k, master_c, master_K, master_cK = bip32_init(self.seed)
@ -202,8 +273,8 @@ class Wallet:
"m/5'/": k5 "m/5'/": k5
} }
self.config.set_key('master_public_keys', self.master_public_keys, True) self.storage.put('master_public_keys', self.master_public_keys, True)
self.config.set_key('master_private_keys', self.master_private_keys, True) self.storage.put('master_private_keys', self.master_private_keys, True)
# create default account # create default account
self.create_account('1','Main account') self.create_account('1','Main account')
@ -220,14 +291,14 @@ class Wallet:
# for safety, we ask the user to enter their seed # for safety, we ask the user to enter their seed
assert seed == self.decode_seed(password) assert seed == self.decode_seed(password)
self.seed = '' self.seed = ''
self.config.set_key('seed', '', True) self.storage.put('seed', '', True)
def deseed_branch(self, k): def deseed_branch(self, k):
# check that parent has no seed # check that parent has no seed
assert self.seed == '' assert self.seed == ''
self.master_private_keys.pop(k) self.master_private_keys.pop(k)
self.config.set_key('master_private_keys', self.master_private_keys, True) self.storage.put('master_private_keys', self.master_private_keys, True)
def account_id(self, account_type, i): def account_id(self, account_type, i):
@ -260,7 +331,7 @@ class Wallet:
account_id, account = self.next_account(account_type) account_id, account = self.next_account(account_type)
addr = account.first_address() addr = account.first_address()
self.first_addresses[k] = addr self.first_addresses[k] = addr
self.config.set_key('first_addresses',self.first_addresses) self.storage.put('first_addresses',self.first_addresses)
return addr return addr
@ -294,26 +365,39 @@ class Wallet:
return account_id, account return account_id, account
def create_account(self, account_type = '1', name = 'unnamed'): def create_account(self, account_type = '1', name = None):
account_id, account = self.next_account(account_type) account_id, account = self.next_account(account_type)
self.accounts[account_id] = account self.accounts[account_id] = account
self.save_accounts() self.save_accounts()
self.labels[account_id] = name if name:
self.config.set_key('labels', self.labels, True) self.labels[account_id] = name
self.storage.put('labels', self.labels, True)
def create_old_account(self):
print self.seed
mpk = OldAccount.mpk_from_seed(self.seed)
self.storage.put('master_public_key', mpk, True)
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
self.save_accounts()
def save_accounts(self): def save_accounts(self):
d = {} d = {}
for k, v in self.accounts.items(): for k, v in self.accounts.items():
d[k] = v.dump() d[k] = v.dump()
self.config.set_key('accounts', d, True) self.storage.put('accounts', d, True)
def load_accounts(self, config): def load_accounts(self):
d = config.get('accounts', {}) d = self.storage.get('accounts', {})
self.accounts = {} self.accounts = {}
for k, v in d.items(): for k, v in d.items():
if '&' in k: if k == 0:
v['mpk'] = self.storage.get('master_public_key')
self.accounts[k] = OldAccount(v)
elif '&' in k:
self.accounts[k] = BIP32_Account_2of2(v) self.accounts[k] = BIP32_Account_2of2(v)
else: else:
self.accounts[k] = BIP32_Account(v) self.accounts[k] = BIP32_Account(v)
@ -339,7 +423,7 @@ class Wallet:
def get_master_public_key(self): def get_master_public_key(self):
raise raise
return self.config.get("master_public_key") return self.storage.get("master_public_key")
def get_master_private_key(self, account, password): def get_master_private_key(self, account, password):
master_k = pw_decode( self.master_private_keys[account], password) master_k = pw_decode( self.master_private_keys[account], password)
@ -379,6 +463,9 @@ class Wallet:
def get_keyID(self, account, sequence): def get_keyID(self, account, sequence):
if account == 0:
return 'old'
rs = self.rebase_sequence(account, sequence) rs = self.rebase_sequence(account, sequence)
dd = [] dd = []
for root, public_sequence in rs: for root, public_sequence in rs:
@ -405,6 +492,12 @@ class Wallet:
out.append( pw_decode( self.imported_keys[address], password ) ) out.append( pw_decode( self.imported_keys[address], password ) )
else: else:
account, sequence = self.get_address_index(address) account, sequence = self.get_address_index(address)
if account == 0:
seed = self.decode_seed(password)
pk = self.accounts[account].get_private_key(seed, sequence)
out.append(pk)
return out
# assert address == self.accounts[account].get_address(*sequence) # assert address == self.accounts[account].get_address(*sequence)
rs = self.rebase_sequence( account, sequence) rs = self.rebase_sequence( account, sequence)
for root, public_sequence in rs: for root, public_sequence in rs:
@ -520,7 +613,7 @@ class Wallet:
def change_gap_limit(self, value): def change_gap_limit(self, value):
if value >= self.gap_limit: if value >= self.gap_limit:
self.gap_limit = value self.gap_limit = value
self.config.set_key('gap_limit', self.gap_limit, True) self.storage.put('gap_limit', self.gap_limit, True)
self.interface.poke('synchronizer') self.interface.poke('synchronizer')
return True return True
@ -533,7 +626,7 @@ class Wallet:
self.accounts[key][0] = addresses self.accounts[key][0] = addresses
self.gap_limit = value self.gap_limit = value
self.config.set_key('gap_limit', self.gap_limit, True) self.storage.put('gap_limit', self.gap_limit, True)
self.save_accounts() self.save_accounts()
return True return True
else: else:
@ -616,13 +709,14 @@ class Wallet:
def synchronize(self): def synchronize(self):
self.create_pending_accounts() if self.master_public_keys:
self.create_pending_accounts()
new = [] new = []
for account in self.accounts.values(): for account in self.accounts.values():
new += self.synchronize_account(account) new += self.synchronize_account(account)
if new: if new:
self.save_accounts() self.save_accounts()
self.config.set_key('addr_history', self.history, True) self.storage.put('addr_history', self.history, True)
return new return new
@ -632,15 +726,15 @@ class Wallet:
def add_contact(self, address, label=None): def add_contact(self, address, label=None):
self.addressbook.append(address) self.addressbook.append(address)
self.config.set_key('contacts', self.addressbook, True) self.storage.put('contacts', self.addressbook, True)
if label: if label:
self.labels[address] = label self.labels[address] = label
self.config.set_key('labels', self.labels, True) self.storage.put('labels', self.labels, True)
def delete_contact(self, addr): def delete_contact(self, addr):
if addr in self.addressbook: if addr in self.addressbook:
self.addressbook.remove(addr) self.addressbook.remove(addr)
self.config.set_key('addressbook', self.addressbook, True) self.storage.put('addressbook', self.addressbook, True)
def fill_addressbook(self): def fill_addressbook(self):
@ -837,6 +931,11 @@ class Wallet:
return inputs, total, fee return inputs, total, fee
def set_fee(self, fee):
if self.fee != fee:
self.fee = fee
self.storage.put('fee_per_kb', self.fee, True)
def estimated_fee(self, inputs): def estimated_fee(self, inputs):
estimated_size = len(inputs) * 180 + 80 # this assumes non-compressed keys estimated_size = len(inputs) * 180 + 80 # this assumes non-compressed keys
fee = self.fee * int(round(estimated_size/1024.)) fee = self.fee * int(round(estimated_size/1024.))
@ -900,7 +999,7 @@ class Wallet:
tx = {} tx = {}
for k,v in self.transactions.items(): for k,v in self.transactions.items():
tx[k] = str(v) tx[k] = str(v)
self.config.set_key('transactions', tx, True) self.storage.put('transactions', tx, True)
def receive_history_callback(self, addr, hist): def receive_history_callback(self, addr, hist):
@ -909,7 +1008,7 @@ class Wallet:
with self.lock: with self.lock:
self.history[addr] = hist self.history[addr] = hist
self.config.set_key('addr_history', self.history, True) self.storage.put('addr_history', self.history, True)
if hist != ['*']: if hist != ['*']:
for tx_hash, tx_height in hist: for tx_hash, tx_height in hist:
@ -1060,28 +1159,28 @@ class Wallet:
if new_password == '': new_password = None if new_password == '': new_password = None
# this will throw an exception if unicode cannot be converted # this will throw an exception if unicode cannot be converted
self.seed = pw_encode( seed, new_password) self.seed = pw_encode( seed, new_password)
self.config.set_key('seed', self.seed, True) self.storage.put('seed', self.seed, True)
self.use_encryption = (new_password != None) self.use_encryption = (new_password != None)
self.config.set_key('use_encryption', self.use_encryption,True) self.storage.put('use_encryption', self.use_encryption,True)
for k in self.imported_keys.keys(): for k in self.imported_keys.keys():
a = self.imported_keys[k] a = self.imported_keys[k]
b = pw_decode(a, old_password) b = pw_decode(a, old_password)
c = pw_encode(b, new_password) c = pw_encode(b, new_password)
self.imported_keys[k] = c self.imported_keys[k] = c
self.config.set_key('imported_keys', self.imported_keys, True) self.storage.put('imported_keys', self.imported_keys, True)
for k, v in self.master_private_keys.items(): for k, v in self.master_private_keys.items():
b = pw_decode(v, old_password) b = pw_decode(v, old_password)
c = pw_encode(b, new_password) c = pw_encode(b, new_password)
self.master_private_keys[k] = c self.master_private_keys[k] = c
self.config.set_key('master_private_keys', self.master_private_keys, True) self.storage.put('master_private_keys', self.master_private_keys, True)
def freeze(self,addr): def freeze(self,addr):
if self.is_mine(addr) and addr not in self.frozen_addresses: if self.is_mine(addr) and addr not in self.frozen_addresses:
self.unprioritize(addr) self.unprioritize(addr)
self.frozen_addresses.append(addr) self.frozen_addresses.append(addr)
self.config.set_key('frozen_addresses', self.frozen_addresses, True) self.storage.put('frozen_addresses', self.frozen_addresses, True)
return True return True
else: else:
return False return False
@ -1089,7 +1188,7 @@ class Wallet:
def unfreeze(self,addr): def unfreeze(self,addr):
if self.is_mine(addr) and addr in self.frozen_addresses: if self.is_mine(addr) and addr in self.frozen_addresses:
self.frozen_addresses.remove(addr) self.frozen_addresses.remove(addr)
self.config.set_key('frozen_addresses', self.frozen_addresses, True) self.storage.put('frozen_addresses', self.frozen_addresses, True)
return True return True
else: else:
return False return False
@ -1098,7 +1197,7 @@ class Wallet:
if self.is_mine(addr) and addr not in self.prioritized_addresses: if self.is_mine(addr) and addr not in self.prioritized_addresses:
self.unfreeze(addr) self.unfreeze(addr)
self.prioritized_addresses.append(addr) self.prioritized_addresses.append(addr)
self.config.set_key('prioritized_addresses', self.prioritized_addresses, True) self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
return True return True
else: else:
return False return False
@ -1106,38 +1205,11 @@ class Wallet:
def unprioritize(self,addr): def unprioritize(self,addr):
if self.is_mine(addr) and addr in self.prioritized_addresses: if self.is_mine(addr) and addr in self.prioritized_addresses:
self.prioritized_addresses.remove(addr) self.prioritized_addresses.remove(addr)
self.config.set_key('prioritized_addresses', self.prioritized_addresses, True) self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
return True return True
else: else:
return False return False
def set_fee(self, fee):
if self.fee != fee:
self.fee = fee
self.config.set_key('fee_per_kb', self.fee, True)
def save(self):
print_error("Warning: wallet.save() is deprecated")
tx = {}
for k,v in self.transactions.items():
tx[k] = str(v)
s = {
'use_change': self.use_change,
'fee_per_kb': self.fee,
'addr_history': self.history,
'labels': self.labels,
'contacts': self.addressbook,
'num_zeros': self.num_zeros,
'frozen_addresses': self.frozen_addresses,
'prioritized_addresses': self.prioritized_addresses,
'gap_limit': self.gap_limit,
'transactions': tx,
}
for k, v in s.items():
self.config.set_key(k,v)
self.config.save()
def set_verifier(self, verifier): def set_verifier(self, verifier):
self.verifier = verifier self.verifier = verifier
@ -1239,12 +1311,26 @@ class Wallet:
return True return True
def start_threads(self, interface, blockchain):
from verifier import TxVerifier
self.interface = interface
self.verifier = TxVerifier(interface, blockchain, self.storage)
self.verifier.start()
self.set_verifier(self.verifier)
self.synchronizer = WalletSynchronizer(self)
self.synchronizer.start()
def stop_threads(self):
self.verifier.stop()
self.synchronizer.stop()
class WalletSynchronizer(threading.Thread): class WalletSynchronizer(threading.Thread):
def __init__(self, wallet, config): def __init__(self, wallet):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.daemon = True self.daemon = True
self.wallet = wallet self.wallet = wallet

View File

@ -58,6 +58,7 @@ setup(name = "Electrum",
'electrum.wallet_bitkey', 'electrum.wallet_bitkey',
'electrum.wallet_factory', 'electrum.wallet_factory',
'electrum.interface', 'electrum.interface',
'electrum.blockchain',
'electrum.commands', 'electrum.commands',
'electrum.mnemonic', 'electrum.mnemonic',
'electrum.simple_config', 'electrum.simple_config',