commit 9d1c31255c235a7506d29514b90628c3927d83be
parent cde1d0f6c07c9c340dcd89c937e8c3c0c2526c8d
Author: thomasv <thomasv@gitorious>
Date: Mon, 2 Sep 2013 11:16:35 +0200
Merge branch '1.9' of git://github.com/spesmilo/electrum into 1.9
Diffstat:
15 files changed, 738 insertions(+), 640 deletions(-)
diff --git a/electrum b/electrum
@@ -107,7 +107,7 @@ if __name__ == '__main__':
util.check_windows_wallet_migration()
config = SimpleConfig(config_options)
-
+ storage = WalletStorage(config)
if len(args)==0:
url = None
@@ -133,10 +133,14 @@ if __name__ == '__main__':
interface.start(wait = False)
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)
interface.stop()
+ blockchain.stop()
# we use daemon threads, their termination is enforced.
# this sleep command gives them time to terminate cleanly.
@@ -145,12 +149,12 @@ if __name__ == '__main__':
# instanciate wallet for command-line
- wallet = Wallet(config)
+ wallet = Wallet(storage)
if cmd not in known_commands:
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("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0)
@@ -197,13 +201,11 @@ if __name__ == '__main__':
if not interface.start(wait=True):
print_msg("Not connected, aborting. Try option -o if you want to restore offline.")
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...")
- WalletSynchronizer(wallet, config).start()
wallet.update()
if wallet.is_found():
print_msg("Recovery successful")
@@ -317,26 +319,21 @@ if __name__ == '__main__':
message = ' '.join(args[min_args:])
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
args = args[0:min_args] + [ message ]
-
-
-
# open session
if cmd not in offline_commands and not options.offline:
interface = Interface(config)
interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
+
if not interface.start(wait=True):
print_msg("Not connected, aborting.")
sys.exit(1)
- wallet.interface = interface
- verifier = WalletVerifier(interface, config)
- verifier.start()
- wallet.set_verifier(verifier)
- synchronizer = WalletSynchronizer(wallet, config)
- synchronizer.start()
+ blockchain = BlockchainVerifier(interface, config)
+ blockchain.start()
+ wallet.start_threads(interface, blockchain)
wallet.update()
- #wallet.save()
+
# 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']:
wallet.config.path = ns
wallet.seed = ''
- wallet.config.set_key('seed', '', True)
+ wallet.storage.put('seed', '', True)
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] = ''
wallet.config.set_key('imported_keys',wallet.imported_keys, True)
print_msg("Done.")
@@ -361,12 +358,12 @@ if __name__ == '__main__':
elif cmd == 'getconfig':
key = args[1]
- print_msg(wallet.config.get(key))
+ print_msg(config.get(key))
elif cmd == 'setconfig':
key, value = args[1:3]
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)
else:
print_msg(False)
@@ -394,8 +391,8 @@ if __name__ == '__main__':
if cmd not in offline_commands and not options.offline:
- verifier.stop()
- synchronizer.stop()
+ wallet.stop_threads()
interface.stop()
+ blockchain.stop()
time.sleep(0.1)
sys.exit(0)
diff --git a/gui/gui_classic.py b/gui/gui_classic.py
@@ -42,7 +42,9 @@ except:
from electrum.wallet import format_satoshis
from electrum.bitcoin import Transaction, is_valid
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 exchange_rate
@@ -225,9 +227,12 @@ class ElectrumWindow(QMainWindow):
def __init__(self, config):
QMainWindow.__init__(self)
+
+ self.config = config
+ self.init_plugins()
+
self._close_electrum = False
self.lite = None
- self.config = config
self.current_account = self.config.get("current_account", None)
self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
@@ -237,14 +242,13 @@ class ElectrumWindow(QMainWindow):
self.build_menu()
self.tray.show()
-
- self.init_plugins()
self.create_status_bar()
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.num_zeros = int(config.get('num_zeros',0))
set_language(config.get('language'))
@@ -287,11 +291,12 @@ class ElectrumWindow(QMainWindow):
tabs.setCurrentIndex (n)
tabs.setCurrentIndex (0)
-
# plugins that need to change the GUI do it here
self.run_hook('init')
+
+
def load_wallet(self, wallet):
import electrum
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('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
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'))
self.setWindowTitle( title )
self.update_wallet()
@@ -312,59 +317,59 @@ class ElectrumWindow(QMainWindow):
# account selector
accounts = self.wallet.get_accounts()
+ self.account_selector.clear()
if len(accounts) > 1:
self.account_selector.addItems([_("All accounts")] + accounts.values())
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):
- wallet_folder = self.wallet.config.path
+ wallet_folder = self.wallet.storage.path
re.sub("(\/\w*.dat)$", "", wallet_folder)
file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
return file_name
def open_wallet(self):
- from electrum import SimpleConfig, Wallet, WalletSynchronizer
filename = self.select_wallet_file()
if not filename:
return
- config = SimpleConfig({'wallet_path': filename})
- if not config.wallet_file_exists:
+ storage = WalletStorage({'wallet_path': filename})
+ if not storage.file_exists:
self.show_message("file not found "+ filename)
return
interface = self.wallet.interface
- verifier = self.wallet.verifier
- self.wallet.synchronizer.stop()
+ blockchain = self.wallet.verifier.blockchain
+ self.wallet.stop_threads()
- self.config = config
-
- # create wallet
- wallet = Wallet(config)
- wallet.interface = interface
- wallet.verifier = verifier
- synchronizer = WalletSynchronizer(wallet, config)
- synchronizer.start()
+ # create new wallet
+ wallet = Wallet(storage)
+ wallet.start_threads(interface, blockchain)
self.load_wallet(wallet)
def new_wallet(self):
- from electrum import SimpleConfig, Wallet, WalletSynchronizer
import installwizard
- wallet_folder = self.wallet.config.path
+ wallet_folder = self.wallet.storage.path
re.sub("(\/\w*.dat)$", "", wallet_folder)
filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
- config = SimpleConfig({'wallet_path': filename})
- assert not config.wallet_file_exists
+ storage = WalletStorage({'wallet_path': filename})
+ 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()
if wallet:
self.load_wallet(wallet)
@@ -374,22 +379,29 @@ class ElectrumWindow(QMainWindow):
def init_menubar(self):
menubar = QMenuBar()
- electrum_menu = menubar.addMenu(_("&File"))
- open_wallet_action = electrum_menu.addAction(_("Open wallet"))
+ file_menu = menubar.addMenu(_("&File"))
+ open_wallet_action = file_menu.addAction(_("&Open"))
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)
- preferences_name = _("Preferences")
- if sys.platform == 'darwin':
- preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
+ wallet_backup = file_menu.addAction(_("&Copy"))
+ wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
+
+ quit_item = file_menu.addAction(_("&Close"))
+ quit_item.triggered.connect(self.close)
+
+ wallet_menu = menubar.addMenu(_("&Wallet"))
- preferences_menu = electrum_menu.addAction(preferences_name)
+ # 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)
- 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.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.triggered.connect(self.do_process_from_text)
- electrum_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))
+ wallet_menu.addSeparator()
show_menu = wallet_menu.addMenu(_("Show"))
@@ -514,14 +520,15 @@ class ElectrumWindow(QMainWindow):
return
-
+
+
def set_label(self, name, text = None):
changed = False
old_text = self.wallet.labels.get(name)
if text:
if old_text != 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
else:
if old_text:
@@ -564,7 +571,7 @@ class ElectrumWindow(QMainWindow):
self.run_hook('timer_actions')
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):
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'\
+ _('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)
- 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)
grid.addWidget(b, 6, 2)
@@ -1339,10 +1342,12 @@ class ElectrumWindow(QMainWindow):
from qt_console import Console
self.console = console = 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({'util' : util, 'bitcoin':bitcoin})
@@ -1356,7 +1361,7 @@ class ElectrumWindow(QMainWindow):
methods[m] = mkfunc(c._run, m)
console.updateNamespace(methods)
- return console
+
def change_account(self,s):
if s == _("All accounts"):
@@ -1389,13 +1394,14 @@ class ElectrumWindow(QMainWindow):
if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
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.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
- sb.addPermanentWidget( self.password_button )
+
+ self.lock_icon = QIcon()
+ self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
+ sb.addPermanentWidget( self.password_button )
+
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
- if 1:#self.wallet.seed:
- sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
+ self.seed_button = 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 )
sb.addPermanentWidget( self.status_button )
@@ -1404,10 +1410,27 @@ class ElectrumWindow(QMainWindow):
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):
from password_dialog import PasswordDialog
d = PasswordDialog(self.wallet, self)
d.run()
+ self.update_lock_icon()
def go_lite(self):
@@ -1420,6 +1443,7 @@ class ElectrumWindow(QMainWindow):
self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
self.lite.main(None)
+
def new_contact_dialog(self):
text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
address = unicode(text)
@@ -1441,7 +1465,7 @@ class ElectrumWindow(QMainWindow):
addr = self.wallet.new_account_address()
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.setReadOnly(True)
vbox.addWidget(e)
@@ -1941,7 +1965,7 @@ class ElectrumWindow(QMainWindow):
nz_label = QLabel(_('Display zeros'))
grid_ui.addWidget(nz_label, 0, 0)
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)
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)
@@ -2097,8 +2121,8 @@ class ElectrumWindow(QMainWindow):
QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
return
- if self.wallet.num_zeros != nz:
- self.wallet.num_zeros = nz
+ if self.num_zeros != nz:
+ self.num_zeros = nz
self.config.set_key('num_zeros', nz, True)
self.update_history_tab()
self.update_receive_tab()
@@ -2155,7 +2179,7 @@ class ElectrumWindow(QMainWindow):
g = self.geometry()
self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
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()
class OpenFileEventFilter(QObject):
@@ -2175,9 +2199,10 @@ class OpenFileEventFilter(QObject):
class ElectrumGui:
- def __init__(self, config, interface, app=None):
+ def __init__(self, config, interface, blockchain, app=None):
self.interface = interface
self.config = config
+ self.blockchain = blockchain
self.windows = []
self.efilter = OpenFileEventFilter(self.windows)
if app is None:
@@ -2186,24 +2211,18 @@ class ElectrumGui:
def main(self, url):
-
- found = self.config.wallet_file_exists
- if not found:
+
+ storage = WalletStorage(self.config)
+ if not storage.file_exists:
import installwizard
- wizard = installwizard.InstallWizard(self.config, self.interface)
+ wizard = installwizard.InstallWizard(self.config, self.interface, self.blockchain, storage)
wallet = wizard.run()
if not wallet:
exit()
else:
- wallet = Wallet(self.config)
-
- wallet.interface = self.interface
+ wallet = Wallet(storage)
- verifier = WalletVerifier(self.interface, self.config)
- verifier.start()
- wallet.set_verifier(verifier)
- synchronizer = WalletSynchronizer(wallet, self.config)
- synchronizer.start()
+ wallet.start_threads(self.interface, self.blockchain)
s = Timer()
s.start()
@@ -2219,7 +2238,6 @@ class ElectrumGui:
self.app.exec_()
- verifier.stop()
- synchronizer.stop()
+ wallet.stop_threads()
diff --git a/gui/gui_text.py b/gui/gui_text.py
@@ -5,12 +5,24 @@ _ = lambda x:x
from electrum.util import format_satoshis, set_verbosity
from electrum.bitcoin import is_valid
+from electrum import Wallet, WalletStorage
+
import tty, sys
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()
curses.noecho()
curses.cbreak()
@@ -24,8 +36,6 @@ class ElectrumGui:
self.set_cursor(0)
self.w = curses.newwin(10, 50, 5, 5)
- self.wallet = wallet
- self.config = config
set_verbosity(False)
self.tab = 0
self.pos = 0
diff --git a/gui/installwizard.py b/gui/installwizard.py
@@ -3,7 +3,7 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
from i18n import _
-from electrum import Wallet, mnemonic, WalletVerifier, WalletSynchronizer
+from electrum import Wallet, mnemonic
from seed_dialog import SeedDialog
from network_dialog import NetworkDialog
@@ -14,10 +14,12 @@ import sys
class InstallWizard(QDialog):
- def __init__(self, config, interface):
+ def __init__(self, config, interface, blockchain, storage):
QDialog.__init__(self)
self.config = config
self.interface = interface
+ self.blockchain = blockchain
+ self.storage = storage
def restore_or_create(self):
@@ -124,6 +126,15 @@ class InstallWizard(QDialog):
wallet.set_up_to_date(False)
wallet.interface.poke('synchronizer')
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():
QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
else:
@@ -137,8 +148,7 @@ class InstallWizard(QDialog):
a = self.restore_or_create()
if not a: exit()
- wallet = Wallet(self.config)
- wallet.interface = self.interface
+ wallet = Wallet(self.storage)
if a =='create':
wallet.init_seed(None)
@@ -170,11 +180,7 @@ class InstallWizard(QDialog):
#self.interface.start(wait = False)
# start wallet threads
- verifier = WalletVerifier(self.interface, self.config)
- verifier.start()
- wallet.set_verifier(verifier)
- synchronizer = WalletSynchronizer(wallet, self.config)
- synchronizer.start()
+ wallet.start_threads(self.interface, self.blockchain)
# generate the first addresses, in case we are offline
diff --git a/gui/network_dialog.py b/gui/network_dialog.py
@@ -44,7 +44,7 @@ class NetworkDialog(QDialog):
if parent:
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:
status = _("Not connected")
server = interface.server
diff --git a/gui/password_dialog.py b/gui/password_dialog.py
@@ -96,9 +96,5 @@ class PasswordDialog(QDialog):
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 )
-
diff --git a/lib/__init__.py b/lib/__init__.py
@@ -1,8 +1,9 @@
from version import ELECTRUM_VERSION
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 verifier import WalletVerifier
+from verifier import TxVerifier
+from blockchain import BlockchainVerifier
from interface import Interface, pick_random_server, DEFAULT_SERVERS
from simple_config import SimpleConfig
import bitcoin
diff --git a/lib/account.py b/lib/account.py
@@ -48,10 +48,13 @@ class Account(object):
class OldAccount(Account):
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
- def __init__(self, mpk, mpk2 = None, mpk3 = None):
- self.mpk = mpk
- self.mpk2 = mpk2
- self.mpk3 = mpk3
+ def __init__(self, v):
+ self.addresses = v.get(0, [])
+ self.change = v.get(1, [])
+ self.mpk = v['mpk'].decode('hex')
+
+ def dump(self):
+ return {0:self.addresses, 1:self.change}
@classmethod
def mpk_from_seed(klass, seed):
@@ -68,48 +71,34 @@ class OldAccount(Account):
seed = hashlib.sha256(seed + oldseed).digest()
return string_to_number( seed )
- def get_sequence(self, sequence, mpk):
- for_change, n = sequence
- return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk.decode('hex') ) )
-
- def get_address(self, sequence):
- if not self.mpk2:
- pubkey = self.get_pubkey(sequence)
- 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"]
+ def get_sequence(self, for_change, n):
+ return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.mpk ) )
+
+ def get_address(self, for_change, n):
+ pubkey = self.get_pubkey(for_change, n)
+ address = public_key_to_bc_address( pubkey.decode('hex') )
return address
- def get_pubkey(self, sequence, mpk=None):
+ def get_pubkey(self, for_change, n):
curve = SECP256k1
- if mpk is None: mpk = self.mpk
- z = self.get_sequence(sequence, mpk)
- master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 )
+ mpk = self.mpk
+ z = self.get_sequence(for_change, n)
+ master_public_key = ecdsa.VerifyingKey.from_string( mpk, curve = SECP256k1 )
pubkey_point = master_public_key.pubkey.point + z*curve.generator
public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
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()
- 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() )
compressed = False
return SecretToASecret( pk, compressed )
- def get_private_key(self, sequence, seed):
- secexp = self.stretch_key(seed)
- return self.get_private_key_from_stretched_exponent(sequence, secexp)
-
- def get_private_keys(self, sequence_list, seed):
+ def get_private_key(self, seed, sequence):
+ for_change, n = sequence
secexp = self.stretch_key(seed)
- return [ self.get_private_key_from_stretched_exponent( sequence, secexp) for sequence in sequence_list]
+ return self.get_private_key_from_stretched_exponent(for_change, n, secexp)
def check_seed(self, seed):
curve = SECP256k1
@@ -121,23 +110,8 @@ class OldAccount(Account):
raise BaseException('Invalid password')
return True
- def get_input_info(self, sequence):
- if not self.mpk2:
- 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
-
+ def redeem_script(self, sequence):
+ return None
class BIP32_Account(Account):
diff --git a/lib/blockchain.py b/lib/blockchain.py
@@ -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
+
diff --git a/lib/interface.py b/lib/interface.py
@@ -396,7 +396,7 @@ class Interface(threading.Thread):
self.unanswered_requests[self.message_id] = method, params, channel
ids.append(self.message_id)
# uncomment to debug
- # print "-->",request
+ # print "-->", request
self.message_id += 1
out += request + '\n'
while out:
diff --git a/lib/simple_config.py b/lib/simple_config.py
@@ -6,6 +6,9 @@ from version import ELECTRUM_VERSION, SEED_VERSION
+
+
+
class SimpleConfig:
"""
The SimpleConfig class is responsible for handling operations involving
@@ -29,46 +32,44 @@ a SimpleConfig instance then reads the wallet file.
# command-line options
self.options_config = options
- self.wallet_config = {}
- self.wallet_file_exists = False
- self.init_path(self.options_config.get('wallet_path'))
- print_error( "path", self.path )
- if self.path:
- self.read_wallet_config(self.path)
+ # init path
+ self.init_path(options)
+
+ print_error( "user dir", self.user_dir)
+
+
+ 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
- if options.get('portable'):
- self.wallet_config['blockchain_headers_path'] = os.path.dirname(self.path)
+ #if options.get('portable'):
+ # self.wallet_config['blockchain_headers_path'] = os.path.dirname(self.path)
-
-
-
- def set_key(self, key, value, save = False):
+ def set_key(self, key, value, save = True):
# find where a setting comes from and save it there
if self.options_config.get(key) is not None:
print "Warning: not changing '%s' because it was passed as a command-line option"%key
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:
if str(self.system_config[key]) != str(value):
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:
- # add key to wallet config
- self.wallet_config[key] = value
- if save: self.save_wallet_config()
+ self.user_config[key] = value
+ if save: self.save_user_config()
+
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
if self.options_config.has_key(key) and self.options_config.get(key) is not None:
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):
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:
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[]."""
if not self.user_dir: return
- name = os.path.join( self.user_dir, 'electrum.conf')
- if os.path.exists(name):
+ path = os.path.join(self.user_dir, "config")
+ if os.path.exists(path):
try:
- import ConfigParser
- except ImportError:
- print "cannot parse electrum.conf. please install ConfigParser"
+ with open(path, "r") as f:
+ data = f.read()
+ except IOError:
return
-
- p = ConfigParser.ConfigParser()
- p.read(name)
try:
- for k, v in p.items('client'):
- self.user_config[k] = v
- except ConfigParser.NoSectionError:
- pass
-
- def init_path(self, path):
- """Set the path of the wallet."""
-
- if not path:
- path = self.get('default_wallet_path')
-
- if path is not None:
- self.path = path
- return
+ d = ast.literal_eval( data ) #parse raw data from reading wallet file
+ except:
+ raise IOError("Cannot read config file.")
- # 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")
+ self.user_config = d
def save_user_config(self):
if not self.user_dir: return
- import ConfigParser
- config = ConfigParser.RawConfigParser()
- config.add_section('client')
- 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")
+ path = os.path.join(self.user_dir, "config")
+ s = repr(self.user_config)
+ f = open(path,"w")
f.write( s )
f.close()
if self.get('gui') != 'android':
import stat
- os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
-
+ os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
diff --git a/lib/verifier.py b/lib/verifier.py
@@ -24,34 +24,29 @@ from bitcoin import *
-class WalletVerifier(threading.Thread):
+class TxVerifier(threading.Thread):
""" Simple Payment Verification """
- def __init__(self, interface, config):
+ def __init__(self, interface, blockchain, storage):
threading.Thread.__init__(self)
self.daemon = True
- self.config = config
+ self.storage = storage
+ self.blockchain = blockchain
self.interface = interface
self.transactions = {} # requested verifications (with height sent by the requestor)
- self.interface.register_channel('verifier')
-
- self.verified_tx = config.get('verified_tx3',{}) # height, timestamp of verified transactions
- self.merkle_roots = config.get('merkle_roots',{}) # hashed by me
-
- self.targets = config.get('targets',{}) # compute targets
+ self.interface.register_channel('txverifier')
+ self.verified_tx = storage.get('verified_tx3',{}) # height, timestamp of verified transactions
+ self.merkle_roots = storage.get('merkle_roots',{}) # hashed by me
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 get_confirmations(self, tx):
""" return the number of confirmations of a monitored transaction. """
with self.lock:
if tx in self.verified_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
elif tx in self.transactions:
@@ -101,67 +96,21 @@ class WalletVerifier(threading.Thread):
with self.lock: return self.running
def run(self):
-
- self.init_headers_file()
- self.set_local_height()
-
with self.lock:
self.running = True
requested_merkle = []
- 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")
-
# request missing tx
- if all_chunks:
- for tx_hash, tx_height in self.transactions.items():
- if tx_hash not in self.verified_tx:
- if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle:
- print_error('requesting merkle', tx_hash)
- self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], 'verifier')
- 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)
+ for tx_hash, tx_height in self.transactions.items():
+ if tx_hash not in self.verified_tx:
+ if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle:
+ print_error('requesting merkle', tx_hash)
+ self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], 'txverifier')
+ requested_merkle.append(tx_hash)
try:
- r = self.interface.get_response('verifier',timeout=1)
+ r = self.interface.get_response('txverifier',timeout=1)
except Queue.Empty:
continue
if not r: continue
@@ -180,138 +129,23 @@ class WalletVerifier(threading.Thread):
self.verify_merkle(tx_hash, result)
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):
tx_height = result.get('block_height')
pos = result.get('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
assert header.get('merkle_root') == self.merkle_roots[tx_hash]
# we passed all the tests
- header = self.read_header(tx_height)
timestamp = header.get('timestamp')
with self.lock:
self.verified_tx[tx_hash] = (tx_height, timestamp, pos)
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')
- 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):
h = hash_decode(target_hash)
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) )
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
+
+
diff --git a/lib/version.py b/lib/version.py
@@ -1,4 +1,4 @@
ELECTRUM_VERSION = "1.9" # version of the client package
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
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -63,39 +63,113 @@ def pw_decode(s, password):
from version import ELECTRUM_VERSION, SEED_VERSION
+class WalletStorage:
+
+ 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, config={}):
- self.config = config
+ def __init__(self, storage):
+
+ self.storage = storage
self.electrum_version = ELECTRUM_VERSION
self.gap_limit_for_change = 3 # constant
# saved fields
- self.seed_version = config.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.seed_version = storage.get('seed_version', SEED_VERSION)
+
+ self.gap_limit = storage.get('gap_limit', 5)
+ 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 = config.get('imported_keys',{})
- self.history = config.get('addr_history',{}) # address -> list(txid, height)
+ self.imported_keys = storage.get('imported_keys',{})
+ self.history = storage.get('addr_history',{}) # address -> list(txid, height)
+ self.fee = int(storage.get('fee_per_kb',20000))
- self.master_public_keys = config.get('master_public_keys',{})
- self.master_private_keys = config.get('master_private_keys', {})
+ self.master_public_keys = storage.get('master_public_keys',{})
+ self.master_private_keys = storage.get('master_private_keys', {})
- self.first_addresses = config.get('first_addresses',{})
+ self.first_addresses = storage.get('first_addresses',{})
- self.load_accounts(config)
+ #if self.seed_version != SEED_VERSION:
+ # raise ValueError("This wallet seed is deprecated. Please restore from seed.")
+
+ self.load_accounts()
self.transactions = {}
- tx = config.get('transactions',{})
+ tx = storage.get('transactions',{})
try:
for k,v in tx.items(): self.transactions[k] = Transaction(v)
except:
@@ -117,9 +191,6 @@ class Wallet:
self.transaction_lock = threading.Lock()
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():
if self.check_new_tx(tx_hash, tx):
self.update_tx_outputs(tx_hash)
@@ -152,13 +223,13 @@ class Wallet:
# store the originally requested keypair into the imported keys table
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
def delete_imported_key(self, addr):
if addr in self.imported_keys:
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):
@@ -169,8 +240,8 @@ class Wallet:
def save_seed(self):
- self.config.set_key('seed', self.seed, True)
- self.config.set_key('seed_version', self.seed_version, True)
+ self.storage.put('seed', self.seed, True)
+ self.storage.put('seed_version', self.seed_version, True)
master_k, master_c, master_K, master_cK = bip32_init(self.seed)
@@ -202,8 +273,8 @@ class Wallet:
"m/5'/": k5
}
- self.config.set_key('master_public_keys', self.master_public_keys, True)
- self.config.set_key('master_private_keys', self.master_private_keys, True)
+ self.storage.put('master_public_keys', self.master_public_keys, True)
+ self.storage.put('master_private_keys', self.master_private_keys, True)
# create default account
self.create_account('1','Main account')
@@ -220,14 +291,14 @@ class Wallet:
# for safety, we ask the user to enter their seed
assert seed == self.decode_seed(password)
self.seed = ''
- self.config.set_key('seed', '', True)
+ self.storage.put('seed', '', True)
def deseed_branch(self, k):
# check that parent has no seed
assert self.seed == ''
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):
@@ -260,7 +331,7 @@ class Wallet:
account_id, account = self.next_account(account_type)
addr = account.first_address()
self.first_addresses[k] = addr
- self.config.set_key('first_addresses',self.first_addresses)
+ self.storage.put('first_addresses',self.first_addresses)
return addr
@@ -294,26 +365,39 @@ class Wallet:
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)
self.accounts[account_id] = account
self.save_accounts()
- self.labels[account_id] = name
- self.config.set_key('labels', self.labels, True)
+ if name:
+ 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):
d = {}
for k, v in self.accounts.items():
d[k] = v.dump()
- self.config.set_key('accounts', d, True)
+ self.storage.put('accounts', d, True)
+
- def load_accounts(self, config):
- d = config.get('accounts', {})
+ def load_accounts(self):
+ d = self.storage.get('accounts', {})
self.accounts = {}
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)
else:
self.accounts[k] = BIP32_Account(v)
@@ -339,7 +423,7 @@ class Wallet:
def get_master_public_key(self):
raise
- return self.config.get("master_public_key")
+ return self.storage.get("master_public_key")
def get_master_private_key(self, account, password):
master_k = pw_decode( self.master_private_keys[account], password)
@@ -379,6 +463,9 @@ class Wallet:
def get_keyID(self, account, sequence):
+ if account == 0:
+ return 'old'
+
rs = self.rebase_sequence(account, sequence)
dd = []
for root, public_sequence in rs:
@@ -405,6 +492,12 @@ class Wallet:
out.append( pw_decode( self.imported_keys[address], password ) )
else:
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)
rs = self.rebase_sequence( account, sequence)
for root, public_sequence in rs:
@@ -520,7 +613,7 @@ class Wallet:
def change_gap_limit(self, value):
if value >= self.gap_limit:
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')
return True
@@ -533,7 +626,7 @@ class Wallet:
self.accounts[key][0] = addresses
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()
return True
else:
@@ -616,13 +709,14 @@ class Wallet:
def synchronize(self):
- self.create_pending_accounts()
+ if self.master_public_keys:
+ self.create_pending_accounts()
new = []
for account in self.accounts.values():
new += self.synchronize_account(account)
if new:
self.save_accounts()
- self.config.set_key('addr_history', self.history, True)
+ self.storage.put('addr_history', self.history, True)
return new
@@ -632,15 +726,15 @@ class Wallet:
def add_contact(self, address, label=None):
self.addressbook.append(address)
- self.config.set_key('contacts', self.addressbook, True)
+ self.storage.put('contacts', self.addressbook, True)
if 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):
if addr in self.addressbook:
self.addressbook.remove(addr)
- self.config.set_key('addressbook', self.addressbook, True)
+ self.storage.put('addressbook', self.addressbook, True)
def fill_addressbook(self):
@@ -837,6 +931,11 @@ class Wallet:
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):
estimated_size = len(inputs) * 180 + 80 # this assumes non-compressed keys
fee = self.fee * int(round(estimated_size/1024.))
@@ -900,7 +999,7 @@ class Wallet:
tx = {}
for k,v in self.transactions.items():
tx[k] = str(v)
- self.config.set_key('transactions', tx, True)
+ self.storage.put('transactions', tx, True)
def receive_history_callback(self, addr, hist):
@@ -909,7 +1008,7 @@ class Wallet:
with self.lock:
self.history[addr] = hist
- self.config.set_key('addr_history', self.history, True)
+ self.storage.put('addr_history', self.history, True)
if hist != ['*']:
for tx_hash, tx_height in hist:
@@ -1060,28 +1159,28 @@ class Wallet:
if new_password == '': new_password = None
# this will throw an exception if unicode cannot be converted
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.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():
a = self.imported_keys[k]
b = pw_decode(a, old_password)
c = pw_encode(b, new_password)
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():
b = pw_decode(v, old_password)
c = pw_encode(b, new_password)
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):
if self.is_mine(addr) and addr not in self.frozen_addresses:
self.unprioritize(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
else:
return False
@@ -1089,7 +1188,7 @@ class Wallet:
def unfreeze(self,addr):
if self.is_mine(addr) and addr in self.frozen_addresses:
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
else:
return False
@@ -1098,7 +1197,7 @@ class Wallet:
if self.is_mine(addr) and addr not in self.prioritized_addresses:
self.unfreeze(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
else:
return False
@@ -1106,38 +1205,11 @@ class Wallet:
def unprioritize(self,addr):
if self.is_mine(addr) and addr in self.prioritized_addresses:
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
else:
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):
self.verifier = verifier
@@ -1239,12 +1311,26 @@ class Wallet:
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):
- def __init__(self, wallet, config):
+ def __init__(self, wallet):
threading.Thread.__init__(self)
self.daemon = True
self.wallet = wallet
diff --git a/setup.py b/setup.py
@@ -58,6 +58,7 @@ setup(name = "Electrum",
'electrum.wallet_bitkey',
'electrum.wallet_factory',
'electrum.interface',
+ 'electrum.blockchain',
'electrum.commands',
'electrum.mnemonic',
'electrum.simple_config',