electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 5444f55e6b497ad8246ada14cff4b941ddcd7996
parent 0d11aa75c48fa8e1daec80afe45ba25f99353ae7
Author: thomasv <thomasv@gitorious>
Date:   Thu, 11 Oct 2012 20:10:12 +0200

big refactoring: command line options and electrum.conf options override settings in wallet file.

Diffstat:
Melectrum | 63+++++++++++++++++++++++++++++++++++----------------------------
Mlib/__init__.py | 3+--
Mlib/gui.py | 10++++++----
Mlib/gui_lite.py | 46+++++++++++++++++++++-------------------------
Mlib/gui_qt.py | 81+++++++++++++++++++++++++++++++++++--------------------------------------------
Mlib/interface.py | 110++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mlib/simple_config.py | 226++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mlib/wallet.py | 199+++++++++++++++++++++----------------------------------------------------------
Mscripts/blocks | 4++--
Mscripts/get_history | 4++--
Mscripts/merchant.py | 4++--
Mscripts/peers | 4++--
Mscripts/watch_address | 4++--
13 files changed, 389 insertions(+), 369 deletions(-)

diff --git a/electrum b/electrum @@ -18,7 +18,6 @@ import re import sys -# import argparse import optparse try: @@ -37,9 +36,9 @@ except ImportError: sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") try: - from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig + from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, SimpleConfig, pick_random_server except ImportError: - from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig + from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, SimpleConfig, pick_random_server from decimal import Decimal @@ -95,33 +94,39 @@ options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize'] +offline_commands = [ 'password', 'mktx', + 'label', 'contacts', + 'help', 'validateaddress', + 'signmessage', 'verifymessage', + 'eval', 'create', 'addresses', + 'import', 'seed', + 'deseed','reseed', + 'freeze','unfreeze', + 'prioritize','unprioritize'] + protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ] if __name__ == '__main__': - # Load simple config class - simple_config = SimpleConfig() - usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands)) parser = optparse.OptionParser(prog=usage) - parser.add_option("-g", "--gui", dest="gui", default=simple_config.config["gui"], help="gui") + parser.add_option("-g", "--gui", dest="gui", help="gui") parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)") parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline") parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses") parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses") parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses") parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee") - parser.add_option("-s", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. if it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet.") + parser.add_option("-F", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. if it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet.") parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet") + parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h") parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http") options, args = parser.parse_args() - proxy = parse_proxy_options(options.proxy) if options.proxy else simple_config.config["proxy"] - wallet = Wallet() - wallet.set_path(options.wallet_path) - wallet.read() + # config is an object passed to the various constructors (wallet, interface, gui) + config = SimpleConfig(options) + wallet = Wallet(config) if len(args)==0: url = None @@ -136,31 +141,31 @@ if __name__ == '__main__': #this entire if/else block is just concerned with importing the #right GUI toolkit based the GUI command line option given if cmd == 'gui': - - if options.gui=='gtk': + pref_gui = config.get('gui','qt') + if pref_gui == 'gtk': try: import lib.gui as gui except ImportError: import electrum.gui as gui - elif options.gui=='qt': + elif pref_gui == 'qt': try: import lib.gui_qt as gui except ImportError: import electrum.gui_qt as gui - elif options.gui == 'lite': + elif pref_gui == 'lite': try: import lib.gui_lite as gui except ImportError: import electrum.gui_lite as gui else: - sys.exit("Error: Unknown GUI: " + options.gui) + sys.exit("Error: Unknown GUI: " + pref_gui ) - gui = gui.ElectrumGui(wallet) - interface = WalletSynchronizer(wallet, True, gui.server_list_changed, proxy) + gui = gui.ElectrumGui(wallet, config) + interface = WalletSynchronizer(wallet, config, True, gui.server_list_changed) interface.start() try: - found = wallet.file_exists + found = config.wallet_file_exists if not found: found = gui.restore_or_create() except SystemExit, e: @@ -180,17 +185,19 @@ if __name__ == '__main__': if cmd not in known_commands: cmd = 'help' - if not wallet.file_exists and cmd not in ['help','create','restore']: + if not config.wallet_file_exists and cmd not in ['help','create','restore']: print "Error: Wallet file not found." print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option" sys.exit(0) if cmd in ['create', 'restore']: - if wallet.file_exists: + if config.wallet_file_exists: sys.exit("Error: Remove the existing wallet first!") password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):") - w_host, w_port, w_protocol = wallet.server.split(':') + server = config.get('server') + if not server: server = pick_random_server() + w_host, w_port, w_protocol = server.split(':') host = raw_input("server (default:%s):"%w_host) port = raw_input("port (default:%s):"%w_port) protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol) @@ -199,7 +206,7 @@ if __name__ == '__main__': if host: w_host = host if port: w_port = port if protocol: w_protocol = protocol - wallet.server = w_host + ':' + w_port + ':' +w_protocol + wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol) if fee: wallet.fee = float(fee) if gap: wallet.gap_limit = int(gap) @@ -216,7 +223,7 @@ if __name__ == '__main__': wallet.seed = str(seed) wallet.init_mpk( wallet.seed ) if not options.offline: - WalletSynchronizer(wallet, proxy=proxy).start() + WalletSynchronizer(wallet, config).start() print "Recovering wallet..." wallet.up_to_date_event.clear() wallet.up_to_date = False @@ -239,7 +246,7 @@ if __name__ == '__main__': print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet." print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:" print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\"" - print "Wallet saved in '%s'"%wallet.path + print "Wallet saved in '%s'"%wallet.config.path if password: wallet.update_password(wallet.seed, None, password) @@ -259,7 +266,7 @@ if __name__ == '__main__': # open session if cmd not in offline_commands and not options.offline: - WalletSynchronizer(wallet, proxy=proxy).start() + WalletSynchronizer(wallet, config).start() wallet.update() wallet.save() diff --git a/lib/__init__.py b/lib/__init__.py @@ -1,4 +1,3 @@ from wallet import Wallet, format_satoshis, prompt_password -from interface import WalletSynchronizer, parse_proxy_options -from interface import TcpStratumInterface +from interface import WalletSynchronizer, Interface, pick_random_server from simple_config import SimpleConfig diff --git a/lib/gui.py b/lib/gui.py @@ -558,12 +558,13 @@ class ElectrumWindow: def show_message(self, msg): show_message(msg, self.window) - def __init__(self, wallet): + def __init__(self, wallet, config): + self.config = config self.wallet = wallet self.funds_error = False # True if not enough funds self.window = MyWindow(gtk.WINDOW_TOPLEVEL) - title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path + title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path if not self.wallet.seed: title += ' [seedless]' self.window.set_title(title) self.window.connect("destroy", gtk.main_quit) @@ -1298,11 +1299,12 @@ class ElectrumWindow: class ElectrumGui(): - def __init__(self, wallet): + def __init__(self, wallet, config): self.wallet = wallet + self.config = config def main(self, url=None): - ew = ElectrumWindow(self.wallet) + ew = ElectrumWindow(self.wallet, self.config) if url: ew.set_url(url) gtk.main() diff --git a/lib/gui_lite.py b/lib/gui_lite.py @@ -13,17 +13,13 @@ except ImportError: qtVersion = qVersion() if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7): app = QApplication(sys.argv) - QMessageBox.warning(None,"Could not start Lite GUI.", "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nElectrum was set to use the 'Qt' GUI") - from simple_config import SimpleConfig - cfg = SimpleConfig() - cfg.set_key("gui", "qt",True) + QMessageBox.warning(None,"Could not start Lite GUI.", "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nPlease use the 'Qt' GUI") sys.exit(0) from decimal import Decimal as D from interface import DEFAULT_SERVERS -from simple_config import SimpleConfig from util import get_resource_path as rsrc from i18n import _ import decimal @@ -61,10 +57,11 @@ def resize_line_edit_width(line_edit, text_input): class ElectrumGui(QObject): - def __init__(self, wallet): + def __init__(self, wallet, config): super(QObject, self).__init__() self.wallet = wallet + self.config = config self.app = QApplication(sys.argv) def main(self, url): @@ -76,7 +73,7 @@ class ElectrumGui(QObject): old_path = QDir.currentPath() actuator.load_theme() - self.mini = MiniWindow(actuator, self.expand) + self.mini = MiniWindow(actuator, self.expand, self.config) driver = MiniDriver(self.wallet, self.mini) # Reset path back to original value now that loading the GUI @@ -88,12 +85,10 @@ class ElectrumGui(QObject): timer = Timer() timer.start() - self.expert = gui_qt.ElectrumWindow(self.wallet) + self.expert = gui_qt.ElectrumWindow(self.wallet, self.config) self.expert.app = self.app self.expert.connect_slots(timer) self.expert.update_wallet() - - self.app.exec_() def server_list_changed(self): @@ -124,10 +119,11 @@ class ElectrumGui(QObject): class MiniWindow(QDialog): - def __init__(self, actuator, expand_callback): + def __init__(self, actuator, expand_callback, config): super(MiniWindow, self).__init__() self.actuator = actuator + self.config = config self.btc_balance = None self.quote_currencies = ["EUR", "USD", "GBP"] @@ -259,11 +255,12 @@ class MiniWindow(QDialog): close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self) close_shortcut.activated.connect(self.close) - self.cfg = SimpleConfig() - g = self.cfg.config["winpos-lite"] + g = self.config.get("winpos-lite",[4, 25, 351, 149]) self.setGeometry(g[0], g[1], g[2], g[3]) - show_history.setChecked(self.cfg.config["history"]) - self.show_history(self.cfg.config["history"]) + + show_hist = self.config.get("gui_show_history",False) + show_history.setChecked(show_hist) + self.show_history(show_hist) self.setWindowIcon(QIcon(":electrum.png")) self.setWindowTitle("Electrum") @@ -282,9 +279,8 @@ class MiniWindow(QDialog): def closeEvent(self, event): g = self.geometry() - self.cfg.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()]) - self.cfg.set_key("history", self.history_list.isVisible()) - self.cfg.save_config() + self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True) + self.config.set_key("history", self.history_list.isVisible(),True) super(MiniWindow, self).closeEvent(event) qApp.quit() @@ -563,7 +559,7 @@ class MiniActuator: def __init__(self, wallet): """Retrieve the gui theme used in previous session.""" self.wallet = wallet - self.theme_name = self.wallet.theme + self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook') self.themes = util.load_theme_paths() def load_theme(self): @@ -587,13 +583,14 @@ class MiniActuator: def change_theme(self, theme_name): """Change theme.""" - self.wallet.theme = self.theme_name = theme_name + self.theme_name = theme_name + self.wallet.config.set_key('litegui_theme',theme_name) self.load_theme() def set_configured_currency(self, set_quote_currency): """Set the inital fiat currency conversion country (USD/EUR/GBP) in the GUI to what it was set to in the wallet.""" - currency = self.wallet.conversion_currency + currency = self.wallet.config.get('conversion_currency') # currency can be none when Electrum is used for the first # time and no setting has been created yet. if currency is not None: @@ -601,7 +598,7 @@ class MiniActuator: def set_config_currency(self, conversion_currency): """Change the wallet fiat currency country.""" - self.wallet.conversion_currency = conversion_currency + self.wallet.config.set_key('conversion_currency',conversion_currency,True) def set_servers_gui_stuff(self, servers_menu, servers_group): self.servers_menu = servers_menu @@ -620,7 +617,7 @@ class MiniActuator: print "Servers loaded." self.servers_list = interface.servers server_names = [details[0] for details in self.servers_list] - current_server = self.wallet.server.split(":")[0] + current_server = interface.server.split(":")[0] for server_name in server_names: server_action = self.servers_menu.addAction(server_name) server_action.setCheckable(True) @@ -661,8 +658,7 @@ class MiniActuator: server_line = "%s:%s:%s" % (server_name, port, protocol) # Should this have exception handling? - self.cfg = SimpleConfig() - self.wallet.set_server(server_line, self.cfg.config["proxy"]) + self.wallet.set_server(server_line, self.config.get(["proxy"])) def copy_address(self, receive_popup): """Copy the wallet addresses into the client.""" diff --git a/lib/gui_qt.py b/lib/gui_qt.py @@ -37,9 +37,7 @@ except: sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'") from wallet import format_satoshis -from simple_config import SimpleConfig import bmp, mnemonic, pyqrnative, qrscanner -from simple_config import SimpleConfig from decimal import Decimal @@ -185,11 +183,14 @@ def ok_cancel_buttons(dialog): class ElectrumWindow(QMainWindow): - def __init__(self, wallet): + def __init__(self, wallet, config): QMainWindow.__init__(self) self.wallet = wallet + self.config = config self.wallet.register_callback(self.update_callback) + self.detailed_view = config.get('qt_detailed_view', False) + self.funds_error = False self.completions = QStringListModel() @@ -204,10 +205,10 @@ class ElectrumWindow(QMainWindow): tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setCentralWidget(tabs) self.create_status_bar() - cfg = SimpleConfig() - g = cfg.config["winpos-qt"] + + g = self.config.get("winpos-qt",[100, 100, 840, 400]) self.setGeometry(g[0], g[1], g[2], g[3]) - title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path + title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path if not self.wallet.seed: title += ' [seedless]' self.setWindowTitle( title ) @@ -696,10 +697,12 @@ class ElectrumWindow(QMainWindow): return w def details_button_text(self): - return _('Hide details') if self.wallet.gui_detailed_view else _('Show details') + return _('Hide details') if self.detailed_view else _('Show details') def toggle_detailed_view(self): - self.wallet.gui_detailed_view = not self.wallet.gui_detailed_view + self.detailed_view = not self.detailed_view + self.config.set_key('qt_detailed_view', self.detailed_view, True) + self.details_button.setText(self.details_button_text()) self.wallet.save() self.update_receive_tab() @@ -731,11 +734,11 @@ class ElectrumWindow(QMainWindow): menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr)) menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr)) menu.addAction(_("Edit label"), lambda: self.edit_label(True)) - if self.wallet.gui_detailed_view: - t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze") - menu.addAction(t, lambda: self.toggle_freeze(addr)) - t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize") - menu.addAction(t, lambda: self.toggle_priority(addr)) + + t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze") + menu.addAction(t, lambda: self.toggle_freeze(addr)) + t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize") + menu.addAction(t, lambda: self.toggle_priority(addr)) menu.exec_(self.receive_list.viewport().mapToGlobal(position)) @@ -790,9 +793,9 @@ class ElectrumWindow(QMainWindow): def update_receive_tab(self): l = self.receive_list l.clear() - l.setColumnHidden(0,not self.wallet.gui_detailed_view) - l.setColumnHidden(3,not self.wallet.gui_detailed_view) - l.setColumnHidden(4,not self.wallet.gui_detailed_view) + l.setColumnHidden(0,not self.detailed_view) + l.setColumnHidden(3,not self.detailed_view) + l.setColumnHidden(4,not self.detailed_view) l.setColumnWidth(0, 50) l.setColumnWidth(1, 310) l.setColumnWidth(2, 250) @@ -803,7 +806,7 @@ class ElectrumWindow(QMainWindow): is_red = False for address in self.wallet.all_addresses(): - if self.wallet.is_change(address) and not self.wallet.gui_detailed_view: + if self.wallet.is_change(address) and not self.detailed_view: continue label = self.wallet.labels.get(address,'') @@ -855,7 +858,7 @@ class ElectrumWindow(QMainWindow): l = self.contacts_list l.clear() - l.setColumnHidden(2, not self.wallet.gui_detailed_view) + l.setColumnHidden(2, not self.detailed_view) l.setColumnWidth(0, 350) l.setColumnWidth(1, 330) l.setColumnWidth(2, 100) @@ -1247,9 +1250,8 @@ class ElectrumWindow(QMainWindow): gap_e.textChanged.connect(lambda: numbify(nz_e,True)) gui = QComboBox() - gui.addItems(['Lite', 'Qt']) - cfg = SimpleConfig() - gui.setCurrentIndex(gui.findText(cfg.config["gui"].capitalize())) + gui.addItems(['Lite', 'Qt', 'Gtk']) + gui.setCurrentIndex(gui.findText(self.config.get("gui","lite").capitalize())) grid.addWidget(QLabel(_('Default GUI') + ':'), 7, 0) grid.addWidget(gui, 7, 1) grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2) @@ -1298,9 +1300,7 @@ class ElectrumWindow(QMainWindow): else: QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK')) - cfg = SimpleConfig() - cfg.set_key("gui", str(gui.currentText()).lower()) - cfg.save_config() + self.config.set_key("gui", str(gui.currentText()).lower(), True) @@ -1312,7 +1312,7 @@ class ElectrumWindow(QMainWindow): status = _("Connected to")+" %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks) else: status = _("Not connected") - server = wallet.server + server = interface.server else: import random status = _("Please choose a server.") @@ -1458,37 +1458,28 @@ class ElectrumWindow(QMainWindow): if not d.exec_(): return server = unicode( host_line.text() ) - try: - if proxy_mode.currentText() != 'NONE': - proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } - else: - proxy = None + if proxy_mode.currentText() != 'NONE': + proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } + else: + proxy = None - cfg = SimpleConfig() - cfg.set_key("proxy", proxy, True) - wallet.set_server(server, proxy) + wallet.config.set_key("proxy", proxy, True) + wallet.config.set_key("server", server, True) + interface.set_server(server, proxy) - except Exception as err: - QMessageBox.information(None, _('Error'), str(err), _('OK')) - if parent == None: - sys.exit(1) - else: - return - return True def closeEvent(self, event): - cfg = SimpleConfig() g = self.geometry() - cfg.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()]) - cfg.save_config() + self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True) event.accept() class ElectrumGui: - def __init__(self, wallet, app=None): + def __init__(self, wallet, config, app=None): self.wallet = wallet + self.config = config if app is None: self.app = QApplication(sys.argv) @@ -1563,7 +1554,7 @@ class ElectrumGui: def main(self,url): s = Timer() s.start() - w = ElectrumWindow(self.wallet) + w = ElectrumWindow(self.wallet, self.config) if url: w.set_url(url) w.app = self.app w.connect_slots(s) diff --git a/lib/interface.py b/lib/interface.py @@ -22,7 +22,7 @@ import threading, traceback, sys, time, json, Queue from version import ELECTRUM_VERSION from util import print_error -from simple_config import SimpleConfig + DEFAULT_TIMEOUT = 5 DEFAULT_SERVERS = [ 'electrum.novit.ro:50001:t', @@ -30,23 +30,10 @@ DEFAULT_SERVERS = [ 'electrum.novit.ro:50001:t', proxy_modes = ['socks4', 'socks5', 'http'] -def replace_keys(obj, old_key, new_key): - if isinstance(obj, dict): - if old_key in obj: - obj[new_key] = obj[old_key] - del obj[old_key] - for elem in obj.itervalues(): - replace_keys(elem, old_key, new_key) - elif isinstance(obj, list): - for elem in obj: - replace_keys(elem, old_key, new_key) - -def old_to_new(d): - replace_keys(d, 'blk_hash', 'block_hash') - replace_keys(d, 'pos', 'index') - replace_keys(d, 'nTime', 'timestamp') - replace_keys(d, 'is_in', 'is_input') - replace_keys(d, 'raw_scriptPubKey', 'raw_output_script') +def pick_random_server(): + print "using random server" + return random.choice( DEFAULT_SERVERS ) + def parse_proxy_options(s): if s.lower() == 'none': return None @@ -65,7 +52,11 @@ def parse_proxy_options(s): proxy["port"] = "8080" if proxy["mode"] == "http" else "1080" return proxy -class Interface(threading.Thread): + + + +class InterfaceAncestor(threading.Thread): + def __init__(self, host, port, proxy=None): threading.Thread.__init__(self) self.daemon = True @@ -134,11 +125,11 @@ class Interface(threading.Thread): -class PollingInterface(Interface): +class PollingInterface(InterfaceAncestor): """ non-persistent connection. synchronous calls""" def __init__(self, host, port, proxy=None): - Interface.__init__(self, host, port, proxy) + InterfaceAncestor.__init__(self, host, port, proxy) self.session_id = None def get_history(self, address): @@ -146,14 +137,6 @@ class PollingInterface(Interface): def poll(self): pass - #if is_new or wallet.remote_url: - # self.was_updated = True - # is_new = wallet.synchronize() - # wallet.update_tx_history() - # wallet.save() - # return is_new - #else: - # return False def run(self): self.is_connected = True @@ -249,11 +232,11 @@ class HttpStratumInterface(PollingInterface): -class TcpStratumInterface(Interface): +class TcpStratumInterface(InterfaceAncestor): """json-rpc over persistent TCP connection, asynchronous""" def __init__(self, host, port, proxy=None): - Interface.__init__(self, host, port, proxy) + InterfaceAncestor.__init__(self, host, port, proxy) def init_socket(self): global proxy_modes @@ -328,38 +311,63 @@ class TcpStratumInterface(Interface): +class Interface(TcpStratumInterface, HttpStratumInterface): + + def __init__(self, config): - -class WalletSynchronizer(threading.Thread): - - def __init__(self, wallet, loop=False, servers_loaded_callback=None, proxy=None): - threading.Thread.__init__(self) - self.daemon = True - self.wallet = wallet - self.loop = loop - self.proxy = proxy - self.init_interface() - self.servers_loaded_callback = servers_loaded_callback - - def init_interface(self): try: - host, port, protocol = self.wallet.server.split(':') + s = config.get('server') + host, port, protocol = s.split(':') port = int(port) except: - self.wallet.pick_random_server() - host, port, protocol = self.wallet.server.split(':') + s = pick_random_server() + host, port, protocol = s.split(':') port = int(port) + proxy = config.get('proxy') + self.server = host + ':%d:%s'%(port, protocol) + #print protocol, host, port if protocol == 't': - InterfaceClass = TcpStratumInterface + TcpStratumInterface.__init__(self, host, port, proxy) elif protocol == 'h': - InterfaceClass = HttpStratumInterface + HttpStratumInterface.__init__(self, host, port, proxy) else: print_error("Error: Unknown protocol") - InterfaceClass = TcpStratumInterface + TcpStratumInterface.__init__(self, host, port, proxy) + + + def set_server(self, server, proxy=None): + # raise an error if the format isnt correct + a,b,c = server.split(':') + b = int(b) + assert c in ['t', 'h'] + # set the server + if server != self.server or proxy != self.proxy: + print "changing server:", server, proxy + self.server = server + self.proxy = proxy + self.is_connected = False # this exits the polling loop + self.poke() + + + + + + +class WalletSynchronizer(threading.Thread): - self.interface = InterfaceClass(host, port, self.proxy) + def __init__(self, wallet, config, loop=False, servers_loaded_callback=None): + threading.Thread.__init__(self) + self.daemon = True + self.wallet = wallet + self.loop = loop + self.config = config + self.init_interface() + self.servers_loaded_callback = servers_loaded_callback + + def init_interface(self): + self.interface = Interface(self.config) self.wallet.interface = self.interface def handle_response(self, r): diff --git a/lib/simple_config.py b/lib/simple_config.py @@ -1,66 +1,176 @@ -import json -import os +import json, ast +import os, ast from util import user_dir +from version import ELECTRUM_VERSION, SEED_VERSION +from interface import parse_proxy_options + + +# old stuff.. should be removed at some point +def replace_keys(obj, old_key, new_key): + if isinstance(obj, dict): + if old_key in obj: + obj[new_key] = obj[old_key] + del obj[old_key] + for elem in obj.itervalues(): + replace_keys(elem, old_key, new_key) + elif isinstance(obj, list): + for elem in obj: + replace_keys(elem, old_key, new_key) + +def old_to_new(d): + replace_keys(d, 'blk_hash', 'block_hash') + replace_keys(d, 'pos', 'index') + replace_keys(d, 'nTime', 'timestamp') + replace_keys(d, 'is_in', 'is_input') + replace_keys(d, 'raw_scriptPubKey', 'raw_output_script') + + + class SimpleConfig: - default_options = { - "gui": "lite", - "proxy": None, - "winpos-qt": [100, 100, 840, 400], - "winpos-lite": [4, 25, 351, 149], - "history": False - } - - def __init__(self): - # Find electrum data folder - self.config_folder = user_dir() - # Read the file - if os.path.exists(self.config_file_path()): - self.load_config() + def __init__(self, options): + + self.wallet_config = {} + self.read_wallet_config(options.wallet_path) + + self.common_config = {} + self.read_common_config() + + self.options_config = {} + + if options.server: self.options_config['server'] = options.server + if options.proxy: self.options_config['proxy'] = parse_proxy_options(options.proxy) + if options.gui: self.options_config['gui'] = options.gui + + + + def set_key(self, key, value, save = False): + # find where a setting comes from and save it there + if self.options_config.get(key): + return + + elif self.wallet_config.get(key): + self.wallet_config[key] = value + if save: self.save_wallet_config() + + elif self.common_config.get(key): + self.common_config[key] = value + if save: self.save_common_config() + + else: + # add key to wallet config + self.wallet_config[key] = value + if save: self.save_wallet_config() + + + def get(self, key, default=None): + # 1. command-line options always override everything + if self.options_config.has_key(key): + # print "found", key, "in options" + out = self.options_config.get(key) + + # 2. configuration file overrides wallet file + elif self.common_config.has_key(key): + out = self.common_config.get(key) + else: - self.config = self.default_options - # Make config directory if it does not yet exist. - if not os.path.exists(self.config_folder): - os.mkdir(self.config_folder) - self.save_config() - - # This is a friendly fallback to the old style default proxy options - if(self.config.get("proxy") is not None and self.config["proxy"]["mode"] == "none"): - self.set_key("proxy", None, True) - - def set_key(self, key, value, save = True): - self.config[key] = value - if save == True: - self.save_config() - - def save_config(self): - if not os.path.exists(self.config_folder): - os.mkdir(self.config_folder) - f = open(self.config_file_path(), "w+") - f.write(json.dumps(self.config)) - - def load_config(self): - f = open(self.config_file_path(), "r") - file_contents = f.read() - if file_contents: - user_config = json.loads(file_contents) - for i in user_config: - self.config[i] = user_config[i] + out = self.wallet_config.get(key) + + if out is None and default is not None: + out = default + return out + + + def is_modifiable(self, key): + if self.options_config.has_key(key) or self.common_config.has_key(key): + return False else: - self.config = self.default_options - self.save_config() - - def config_file_path(self): - return "%s" % (self.config_folder + "/config.json") - - def __init__(self): - # Find electrum data folder - self.config_folder = user_dir() - self.config = self.default_options - # Read the file - if os.path.exists(self.config_file_path()): - self.load_config() - self.save_config() + return True + + + def read_common_config(self): + for name in [ os.path.join( user_dir(), 'electrum.conf') , '/etc/electrum.conf']: + if os.path.exists(name): + from interface import parse_proxy_options + try: + import ConfigParser + except: + print "cannot parse electrum.conf. please install ConfigParser" + return + + p = ConfigParser.ConfigParser() + p.read(name) + try: + self.common_config['server'] = p.get('interface','server') + except: + pass + try: + self.common_config['proxy'] = parse_proxy_options(p.get('interface','proxy')) + except: + pass + break + + + + def init_path(self, wallet_path): + """Set the path of the wallet.""" + if wallet_path is not None: + self.path = wallet_path + return + + # Look for wallet file in the default data directory. + # Keeps backwards compatibility. + wallet_dir = user_dir() + + # Make wallet directory if it does not yet exist. + if not os.path.exists(wallet_dir): + os.mkdir(wallet_dir) + self.path = os.path.join(wallet_dir, "electrum.dat") + + + + def save_common_config(self): + s = repr(self.common_config) + # todo: decide what to do + print "not saving settings in common config:", s + + + + def read_wallet_config(self, path): + """Read the contents of the wallet file.""" + self.wallet_file_exists = False + self.init_path(path) + 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 + old_to_new(d) + except: + raise IOError("Cannot read wallet file.") + + self.wallet_config = d + self.wallet_file_exists = True + + + def set_interface(self, interface): + pass + + def set_gui(self, gui): + pass + + def save(self): + self.save_wallet_config() + + def save_wallet_config(self): + s = repr(self.wallet_config) + f = open(self.path,"w") + f.write( s ) + f.close() + import stat + os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE) diff --git a/lib/wallet.py b/lib/wallet.py @@ -273,49 +273,40 @@ def format_satoshis(x, is_diff=False, num_zeros = 0): from version import ELECTRUM_VERSION, SEED_VERSION -from interface import DEFAULT_SERVERS - class Wallet: - def __init__(self): + def __init__(self, config={}): + self.config = config self.electrum_version = ELECTRUM_VERSION - self.seed_version = SEED_VERSION self.update_callbacks = [] - self.gap_limit = 5 # configuration - self.use_change = True - self.fee = 100000 - self.num_zeros = 0 - self.master_public_key = '' - self.conversion_currency = None - self.theme = "Cleanlook" - # saved fields - self.use_encryption = False - self.addresses = [] # receiving addresses visible for user - self.change_addresses = [] # addresses used as change - self.seed = '' # encrypted - self.history = {} - self.labels = {} # labels for addresses and transactions - self.aliases = {} # aliases for addresses - self.authorities = {} # trusted addresses - self.frozen_addresses = [] - self.prioritized_addresses = [] - self.gui_detailed_view = False - - self.receipts = {} # signed URIs - self.receipt = None # next receipt - self.addressbook = [] # outgoing addresses, for payments - self.debug_server = False # write server communication debug info to stdout + 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',100000)) + self.num_zeros = int(config.get('num_zeros',0)) + self.master_public_key = config.get('master_public_key','').decode('hex') + self.use_encryption = config.get('use_encryption', False) + self.addresses = config.get('addresses', []) # receiving addresses visible for user + self.change_addresses = config.get('change_addresses', []) # addresses used as change + self.seed = config.get('seed', '') # encrypted + self.history = config.get('history',{}) + self.labels = config.get('labels',{}) # labels for addresses and transactions + self.aliases = config.get('aliases', {}) # aliases for addresses + self.authorities = config.get('authorities', {}) # trusted addresses + self.frozen_addresses = config.get('frozen_addresses',[]) + self.prioritized_addresses = config.get('prioritized_addresses',[]) + self.receipts = config.get('receipts',{}) # signed URIs + self.addressbook = config.get('contacts', []) # outgoing addresses, for payments + self.imported_keys = config.get('imported_keys',{}) # not saved + self.receipt = None # next receipt self.tx_history = {} - - self.imported_keys = {} - self.was_updated = True self.blocks = -1 self.banner = '' @@ -329,7 +320,10 @@ class Wallet: self.lock = threading.Lock() self.tx_event = threading.Event() - self.pick_random_server() + self.update_tx_history() + if self.seed_version != SEED_VERSION: + raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.") + def register_callback(self, update_callback): with self.lock: @@ -340,38 +334,9 @@ class Wallet: callbacks = self.update_callbacks[:] [update() for update in callbacks] - def pick_random_server(self): - self.server = random.choice( DEFAULT_SERVERS ) # random choice when the wallet is created - def is_up_to_date(self): return self.interface.responses.empty() and not self.interface.unanswered_requests - def set_server(self, server, proxy=None): - # raise an error if the format isnt correct - a,b,c = server.split(':') - b = int(b) - assert c in ['t', 'h', 'n'] - # set the server - if server != self.server or proxy != self.interface.proxy: - self.server = server - self.save() - self.interface.proxy = proxy - self.interface.is_connected = False # this exits the polling loop - self.interface.poke() - - def set_path(self, wallet_path): - """Set the path of the wallet.""" - if wallet_path is not None: - self.path = wallet_path - return - # Look for wallet file in the default data directory. - # Keeps backwards compatibility. - wallet_dir = user_dir() - - # Make wallet directory if it does not yet exist. - if not os.path.exists(wallet_dir): - os.mkdir(wallet_dir) - self.path = os.path.join(wallet_dir, "electrum.dat") def import_key(self, keypair, password): address, key = keypair.split(':') @@ -390,6 +355,7 @@ class Wallet: raise BaseException('Address does not match private key') self.imported_keys[address] = self.pw_encode( key, password ) + def new_seed(self, password): seed = "%032x"%ecdsa.util.randrange( pow(2,128) ) #self.init_mpk(seed) @@ -630,91 +596,6 @@ class Wallet: self.update_tx_labels() - def save(self): - # TODO: Need special config storage class. Should not be mixed - # up with the wallet. - # Settings should maybe be stored in a flat ini file. - s = { - 'seed_version': self.seed_version, - 'use_encryption': self.use_encryption, - 'use_change': self.use_change, - 'master_public_key': self.master_public_key.encode('hex'), - 'fee': self.fee, - 'server': self.server, - 'seed': self.seed, - 'addresses': self.addresses, - 'change_addresses': self.change_addresses, - 'history': self.history, - 'labels': self.labels, - 'contacts': self.addressbook, - 'imported_keys': self.imported_keys, - 'aliases': self.aliases, - 'authorities': self.authorities, - 'receipts': self.receipts, - 'num_zeros': self.num_zeros, - 'frozen_addresses': self.frozen_addresses, - 'prioritized_addresses': self.prioritized_addresses, - 'gui_detailed_view': self.gui_detailed_view, - 'gap_limit': self.gap_limit, - 'debug_server': self.debug_server, - 'conversion_currency': self.conversion_currency, - 'theme': self.theme - } - f = open(self.path,"w") - f.write( repr(s) ) - f.close() - import stat - os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE) - - def read(self): - """Read the contents of the wallet file.""" - import interface - - self.file_exists = False - 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 - interface.old_to_new(d) - - self.seed_version = d.get('seed_version') - self.master_public_key = d.get('master_public_key').decode('hex') - self.use_encryption = d.get('use_encryption') - self.use_change = bool(d.get('use_change', True)) - self.fee = int(d.get('fee')) - self.seed = d.get('seed') - self.server = d.get('server') - self.addresses = d.get('addresses') - self.change_addresses = d.get('change_addresses') - self.history = d.get('history') - self.labels = d.get('labels') - self.addressbook = d.get('contacts') - self.imported_keys = d.get('imported_keys', {}) - self.aliases = d.get('aliases', {}) - self.authorities = d.get('authorities', {}) - self.receipts = d.get('receipts', {}) - self.num_zeros = d.get('num_zeros', 0) - self.frozen_addresses = d.get('frozen_addresses', []) - self.prioritized_addresses = d.get('prioritized_addresses', []) - self.gui_detailed_view = d.get('gui_detailed_view', False) - self.gap_limit = d.get('gap_limit', 5) - self.debug_server = d.get('debug_server', False) - self.conversion_currency = d.get('conversion_currency', 'USD') - self.theme = d.get('theme', 'Cleanlook') - except: - raise IOError("Cannot read wallet file.") - - self.update_tx_history() - - if self.seed_version != SEED_VERSION: - raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.") - - self.file_exists = True - - def get_address_flags(self, addr): flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-" @@ -1134,3 +1015,29 @@ class Wallet: return True else: return False + + def save(self): + s = { + 'seed_version': self.seed_version, + 'use_encryption': self.use_encryption, + 'use_change': self.use_change, + 'master_public_key': self.master_public_key.encode('hex'), + 'fee': self.fee, + 'seed': self.seed, + 'addresses': self.addresses, + 'change_addresses': self.change_addresses, + 'history': self.history, + 'labels': self.labels, + 'contacts': self.addressbook, + 'imported_keys': self.imported_keys, + 'aliases': self.aliases, + 'authorities': self.authorities, + 'receipts': self.receipts, + 'num_zeros': self.num_zeros, + 'frozen_addresses': self.frozen_addresses, + 'prioritized_addresses': self.prioritized_addresses, + 'gap_limit': self.gap_limit, + } + for k, v in s.items(): + self.config.set_key(k,v) + self.config.save() diff --git a/scripts/blocks b/scripts/blocks @@ -1,8 +1,8 @@ #!/usr/bin/env python -from electrum import TcpStratumInterface +from electrum import Interface -i = TcpStratumInterface('electrum.novit.ro', 50001) +i = Interface({'server':'electrum.novit.ro:50001:t'}) i.init_socket() i.start() i.send([('blockchain.numblocks.subscribe',[])]) diff --git a/scripts/get_history b/scripts/get_history @@ -1,7 +1,7 @@ #!/usr/bin/env python import sys -from electrum import TcpStratumInterface +from electrum import Interface try: addr = sys.argv[1] @@ -9,7 +9,7 @@ except: print "usage: get_history <bitcoin_address>" sys.exit(1) -i = TcpStratumInterface('electrum.novit.ro', 50001) +i = Interface({'server':'electrum.novit.ro:50001:t'}) i.init_socket() i.start() i.send([('blockchain.address.get_history',[addr])]) diff --git a/scripts/merchant.py b/scripts/merchant.py @@ -21,7 +21,7 @@ import time, thread, sys, socket, os import urllib2,json import MySQLdb as mdb import Queue -from electrum import Wallet, TcpStratumInterface +from electrum import Wallet, Interface import ConfigParser config = ConfigParser.ConfigParser() @@ -157,7 +157,7 @@ if __name__ == '__main__': print "using database", db_name conn = mdb.connect(db_instance, db_user, db_password, db_name); - i = TcpStratumInterface(electrum_server, 50001) + i = Interface({'server':"%s:%d:t"%(electrum_server, 50001)}) i.init_socket() i.start() diff --git a/scripts/peers b/scripts/peers @@ -1,8 +1,8 @@ #!/usr/bin/env python -from electrum import TcpStratumInterface +from electrum import Interface -i = TcpStratumInterface('electrum.novit.ro', 50001) +i = Interface({'server':'electrum.novit.ro:50001:t'}) i.init_socket() i.start() i.send([('server.peers.subscribe',[])]) diff --git a/scripts/watch_address b/scripts/watch_address @@ -1,7 +1,7 @@ #!/usr/bin/env python import sys -from electrum import TcpStratumInterface +from electrum import Interface try: addr = sys.argv[1] @@ -9,7 +9,7 @@ except: print "usage: watch_address <bitcoin_address>" sys.exit(1) -i = TcpStratumInterface('electrum.novit.ro', 50001) +i = Interface({'server':'electrum.novit.ro:50001:t'}) i.init_socket() i.start() i.send([('blockchain.address.subscribe',[addr])])