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()
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)

View File

@ -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))
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)
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 = Wallet(storage)
wallet.interface = self.interface
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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 )

View File

@ -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

View File

@ -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_sequence(self, for_change, n):
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.mpk ) )
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_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):
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)
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]
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):

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
ids.append(self.message_id)
# uncomment to debug
# print "-->",request
# print "-->", request
self.message_id += 1
out += request + '\n'
while out:

View File

@ -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
d = ast.literal_eval( data ) #parse raw data from reading wallet file
except:
raise IOError("Cannot read config file.")
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
# 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)

View File

@ -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

View File

@ -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

View File

@ -63,39 +63,113 @@ def pw_decode(s, password):
from version import ELECTRUM_VERSION, SEED_VERSION
class Wallet:
def __init__(self, config={}):
class WalletStorage:
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.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.imported_keys = config.get('imported_keys',{})
self.history = config.get('addr_history',{}) # address -> list(txid, height)
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 = storage.get('imported_keys',{})
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
self.master_public_keys = config.get('master_public_keys',{})
self.master_private_keys = config.get('master_private_keys', {})
self.fee = int(storage.get('fee_per_kb',20000))
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 = {}
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

View File

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