From aa6f631f2e14391229425c5ffba1a07f4dd471be Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Wed, 19 Sep 2012 17:37:20 +0700 Subject: [PATCH 01/13] Added SOCKS support, with cmdline and SimpleConfig options --- electrum | 9 +++++--- lib/__init__.py | 2 +- lib/interface.py | 50 +++++++++++++++++++++++++++++++++++--------- lib/simple_config.py | 2 +- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/electrum b/electrum index 32ec670fe..fde255bc1 100755 --- a/electrum +++ b/electrum @@ -37,9 +37,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, SimpleConfig + from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig except ImportError: - from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, SimpleConfig + from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig from decimal import Decimal @@ -116,8 +116,11 @@ if __name__ == '__main__': 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("-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("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet") + parser.add_option("-p", "--proxy", dest="proxy", default=simple_config.config["proxy"], help="set proxy [type:]host[:port], where type is socks4,socks5 or http") options, args = parser.parse_args() + if type(options.proxy) == type(''): + options.proxy = parse_proxy_options(options.proxy) wallet = Wallet() wallet.set_path(options.wallet_path) @@ -179,7 +182,7 @@ if __name__ == '__main__': sys.exit("Error: Unknown GUI: " + options.gui) gui = gui.ElectrumGui(wallet) - interface = WalletSynchronizer(wallet, True, gui.server_list_changed) + interface = WalletSynchronizer(wallet, True, gui.server_list_changed, options.proxy) interface.start() try: diff --git a/lib/__init__.py b/lib/__init__.py index a9461ac88..3618ccfe3 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,4 +1,4 @@ from wallet import Wallet, format_satoshis, prompt_password -from interface import WalletSynchronizer +from interface import WalletSynchronizer, parse_proxy_options from interface import TcpStratumInterface from simple_config import SimpleConfig diff --git a/lib/interface.py b/lib/interface.py index 6a28cf8e6..185a2d47e 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -29,6 +29,7 @@ DEFAULT_SERVERS = [ 'ecdsa.org:50001:t', 'uncle-enzo.info:50001:t', 'electrum.bytesized-hosting.com:50001:t'] # list of default servers +proxy_modes = ['off', 'socks4', 'socks5', 'http' ] def replace_keys(obj, old_key, new_key): if isinstance(obj, dict): @@ -48,13 +49,29 @@ def old_to_new(d): replace_keys(d, 'is_in', 'is_input') replace_keys(d, 'raw_scriptPubKey', 'raw_output_script') +def parse_proxy_options(s): + proxy = { "mode":"socks5", "host":"localhost" } + args = s.split(':') + n = 0 + if proxy_modes.count(args[n]) == 1: + proxy["mode"] = args[n] + n += 1 + if len(args) > n: + proxy["host"] = args[n] + n += 1 + if len(args) > n: + proxy["port"] = args[n] + else: + proxy["port"] = "8080" if proxy["mode"] == "http" else "1080" + return proxy class Interface(threading.Thread): - def __init__(self, host, port, debug_server): + def __init__(self, host, port, debug_server, proxy): threading.Thread.__init__(self) self.daemon = True self.host = host self.port = port + self.proxy = proxy self.servers = [] # actual list from IRC self.rtime = 0 @@ -122,8 +139,8 @@ class Interface(threading.Thread): class PollingInterface(Interface): """ non-persistent connection. synchronous calls""" - def __init__(self, host, port, debug_server): - Interface.__init__(self, host, port, debug_server) + def __init__(self, host, port, debug_server, proxy): + Interface.__init__(self, host, port, debug_server, proxy) self.session_id = None self.debug_server = debug_server @@ -174,7 +191,11 @@ class HttpStratumInterface(PollingInterface): def send(self, messages): import urllib2, json, time, cookielib - + + if self.proxy["mode"] != "off": + import socks + socks.setdefaultproxy(proxy_modes.index(self.proxy["mode"]), self.proxy["host"], int(self.proxy["port"]) ) + socks.wrapmodule(urllib2) cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) @@ -233,16 +254,23 @@ class HttpStratumInterface(PollingInterface): class TcpStratumInterface(Interface): """json-rpc over persistent TCP connection, asynchronous""" - def __init__(self, host, port, debug_server): - Interface.__init__(self, host, port, debug_server) + def __init__(self, host, port, debug_server, proxy): + Interface.__init__(self, host, port, debug_server, proxy) self.debug_server = debug_server def init_socket(self): - self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) + global proxy_modes + if self.proxy["mode"] == "off": + self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) + else: + import socks + self.s = socks.socksocket() + print "Using Proxy", self.proxy + self.s.setproxy(proxy_modes.index(self.proxy["mode"]), self.proxy["host"], int(self.proxy["port"]) ) self.s.settimeout(60) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) try: - self.s.connect(( self.host, self.port)) + self.s.connect(( self.host.encode('ascii'), int(self.port))) self.is_connected = True self.send([('server.version', [ELECTRUM_VERSION])]) print "Connected to %s:%d"%(self.host,self.port) @@ -306,13 +334,15 @@ class TcpStratumInterface(Interface): class WalletSynchronizer(threading.Thread): - def __init__(self, wallet, loop=False, servers_loaded_callback=None): + 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: @@ -332,7 +362,7 @@ class WalletSynchronizer(threading.Thread): print_error("Error: Unknown protocol") InterfaceClass = TcpStratumInterface - self.interface = InterfaceClass(host, port, self.wallet.debug_server) + self.interface = InterfaceClass(host, port, self.wallet.debug_server, self.proxy) self.wallet.interface = self.interface def handle_response(self, r): diff --git a/lib/simple_config.py b/lib/simple_config.py index 06f78bdc1..3ade03305 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -3,7 +3,7 @@ import os from util import user_dir class SimpleConfig: - default_options = {"gui": "lite"} + default_options = {"gui": "lite", "proxy": { "mode": "off", "host":"localhost", "port":"8080" } } def set_key(self, key, value, save = True): self.config[key] = value From 1af17baafb5bcbfc77046bda345f742608f40b56 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Wed, 19 Sep 2012 17:37:43 +0700 Subject: [PATCH 02/13] sockssipy support module provides SOCKS and HTTP proxy wrapper --- lib/socks.py | 382 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 lib/socks.py diff --git a/lib/socks.py b/lib/socks.py new file mode 100644 index 000000000..a6c0d70e6 --- /dev/null +++ b/lib/socks.py @@ -0,0 +1,382 @@ +"""SocksiPy - Python SOCKS module. +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +""" + +""" + +Minor modifications made by Christopher Gilbert (http://motomastyle.com/) +for use in PyLoris (http://pyloris.sourceforge.net/) + +Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) +mainly to merge bug fixes found in Sourceforge + +""" + +import socket +import struct +import sys + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 + +_defaultproxy = None +_orgsocket = socket.socket + +class ProxyError(Exception): pass +class GeneralProxyError(ProxyError): pass +class Socks5AuthError(ProxyError): pass +class Socks5Error(ProxyError): pass +class Socks4Error(ProxyError): pass +class HTTPError(ProxyError): pass + +_generalerrors = ("success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input") + +_socks5errors = ("succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error") + +_socks5autherrors = ("succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") + +_socks4errors = ("request granted", + "request rejected or failed", + "request rejected because SOCKS server cannot connect to identd on the client", + "request rejected because the client program and identd report different user-ids", + "unknown error") + +def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype, addr, port, rdns, username, password) + +def wrapmodule(module): + """wrapmodule(module) + Attempts to replace a module's socket library with a SOCKS socket. Must set + a default proxy using setdefaultproxy(...) first. + This will only work on modules that import socket directly into the namespace; + most of the Python Standard Library falls into this category. + """ + if _defaultproxy != None: + module.socket.socket = socksocket + else: + raise GeneralProxyError((4, "no proxy specified")) + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): + _orgsocket.__init__(self, family, type, proto, _sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + + def __recvall(self, count): + """__recvall(count) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + data = self.recv(count) + while len(data) < count: + d = self.recv(count-len(data)) + if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) + data = data + d + return data + + def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + """ + self.__proxy = (proxytype, addr, port, rdns, username, password) + + def __negotiatesocks5(self, destaddr, destport): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1:2] == chr(0x00).encode(): + # No authentication is required + pass + elif chosenauth[1:2] == chr(0x02).encode(): + # Okay, we need to perform a basic username/password + # authentication. + self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) + authstat = self.__recvall(2) + if authstat[0:1] != chr(0x01).encode(): + # Bad response + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if authstat[1:2] != chr(0x00).encode(): + # Authentication failed + self.close() + raise Socks5AuthError((3, _socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == chr(0xFF).encode(): + raise Socks5AuthError((2, _socks5autherrors[2])) + else: + raise GeneralProxyError((1, _generalerrors[1])) + # Now we can request the actual connection + req = struct.pack('BBB', 0x05, 0x01, 0x00) + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + ipaddr = socket.inet_aton(destaddr) + req = req + chr(0x01).encode() + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3]: + # Resolve remotely + ipaddr = None + req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + chr(0x01).encode() + ipaddr + req = req + struct.pack(">H", destport) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2])<=8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + boundaddr = self.__recvall(4) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + boundaddr = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + self.__proxysockname = (boundaddr, boundport) + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return _orgsocket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def __negotiatesocks4(self,destaddr,destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3]: + ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + chr(0x00).encode() + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv: + req = req + destaddr + chr(0x00).encode() + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0:1] != chr(0x00).encode(): + # Bad data + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if resp[1:2] != chr(0x5A).encode(): + # Server returned an error + self.close() + if ord(resp[1:2]) in (91, 92, 93): + self.close() + raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) + else: + raise Socks4Error((94, _socks4errors[4])) + # Get the bound address/port + self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def __negotiatehttp(self, destaddr, destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if not self.__proxy[3]: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n".encode()) == -1: + resp = resp + self.recv(1) + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ".encode(), 2) + if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode, statusline[2])) + self.__proxysockname = ("0.0.0.0", 0) + self.__proxypeername = (addr, destport) + + def connect(self, destpair): + """connect(self, despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): + raise GeneralProxyError((5, _generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatesocks4(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatehttp(destpair[0], destpair[1]) + elif self.__proxy[0] == None: + _orgsocket.connect(self, (destpair[0], destpair[1])) + else: + raise GeneralProxyError((4, _generalerrors[4])) From 83bb644135bab738572a39d3ca8fd373fd277469 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Wed, 19 Sep 2012 22:29:58 +0700 Subject: [PATCH 03/13] Added proxy options to network dialog --- lib/gui_qt.py | 19 ++++++++++++++++++- lib/interface.py | 7 ++++--- lib/simple_config.py | 2 +- lib/wallet.py | 5 +++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/gui_qt.py b/lib/gui_qt.py index c619df375..7b89ab1fb 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1380,6 +1380,22 @@ class ElectrumWindow(QMainWindow): hbox.addWidget(radio2) vbox.addLayout(hbox) + + hbox = QHBoxLayout() + proxy_mode = QComboBox() + proxy_host = QLineEdit() + proxy_port = QLineEdit() + proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) + proxy_mode.setCurrentIndex(proxy_mode.findText(str(interface.proxy["mode"]).upper())) + proxy_host.setText(interface.proxy["host"]) + proxy_port.setText(interface.proxy["port"]) + hbox.addWidget(QLabel(_('Proxy') + ':')) + hbox.addWidget(proxy_mode) + hbox.addWidget(proxy_host) + hbox.addWidget(proxy_port) + vbox.addLayout(hbox) + + hbox = QHBoxLayout() if wallet.interface.servers: label = _('Active Servers') @@ -1413,7 +1429,8 @@ class ElectrumWindow(QMainWindow): server = unicode( host_line.text() ) try: - wallet.set_server(server) + proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } + wallet.set_server(server, proxy) except: QMessageBox.information(None, _('Error'), 'error', _('OK')) if parent == None: diff --git a/lib/interface.py b/lib/interface.py index 185a2d47e..83722de9a 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -29,7 +29,7 @@ DEFAULT_SERVERS = [ 'ecdsa.org:50001:t', 'uncle-enzo.info:50001:t', 'electrum.bytesized-hosting.com:50001:t'] # list of default servers -proxy_modes = ['off', 'socks4', 'socks5', 'http' ] +proxy_modes = ['none', 'socks4', 'socks5', 'http' ] def replace_keys(obj, old_key, new_key): if isinstance(obj, dict): @@ -192,7 +192,7 @@ class HttpStratumInterface(PollingInterface): def send(self, messages): import urllib2, json, time, cookielib - if self.proxy["mode"] != "off": + if self.proxy["mode"] != "none": import socks socks.setdefaultproxy(proxy_modes.index(self.proxy["mode"]), self.proxy["host"], int(self.proxy["port"]) ) socks.wrapmodule(urllib2) @@ -260,7 +260,7 @@ class TcpStratumInterface(Interface): def init_socket(self): global proxy_modes - if self.proxy["mode"] == "off": + if self.proxy["mode"] == "none": self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) else: import socks @@ -460,6 +460,7 @@ class WalletSynchronizer(threading.Thread): if self.loop: time.sleep(5) # Server has been changed. Copy callback for new interface. + self.proxy = self.interface.proxy self.init_interface() self.start_interface() continue diff --git a/lib/simple_config.py b/lib/simple_config.py index 3ade03305..e33efd5b0 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -3,7 +3,7 @@ import os from util import user_dir class SimpleConfig: - default_options = {"gui": "lite", "proxy": { "mode": "off", "host":"localhost", "port":"8080" } } + default_options = {"gui": "lite", "proxy": { "mode": "none", "host":"localhost", "port":"8080" } } def set_key(self, key, value, save = True): self.config[key] = value diff --git a/lib/wallet.py b/lib/wallet.py index 80d285998..c6e41295a 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -347,15 +347,16 @@ class Wallet: def is_up_to_date(self): return self.interface.responses.empty() and not self.interface.unanswered_requests - def set_server(self, server): + def set_server(self, server, proxy): # 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: + 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() From af750d9363bd58eaccb60fcdf5635ab402c77001 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Thu, 20 Sep 2012 15:30:08 +0700 Subject: [PATCH 04/13] Fix for wallet restore when offline --- electrum | 3 +++ 1 file changed, 3 insertions(+) diff --git a/electrum b/electrum index fde255bc1..ce5159a2c 100755 --- a/electrum +++ b/electrum @@ -251,6 +251,8 @@ if __name__ == '__main__': print "Recovery successful" else: print_error("Warning: Found no history for this wallet") + else: + wallet.synchronize() wallet.fill_addressbook() wallet.save() print_error("Wallet saved in '" + wallet.path) @@ -413,6 +415,7 @@ if __name__ == '__main__': wallet.save() elif cmd in [ 'addresses']: + for addr in wallet.all_addresses(): if options.show_all or not wallet.is_change(addr): From 6e0b3620d23da54a0ea9a43cf568432bacb0852a Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Thu, 20 Sep 2012 16:55:15 +0700 Subject: [PATCH 05/13] Revert "Added proxy options to network dialog" This reverts commit 83bb644135bab738572a39d3ca8fd373fd277469. --- lib/gui_qt.py | 19 +------------------ lib/interface.py | 7 +++---- lib/simple_config.py | 2 +- lib/wallet.py | 5 ++--- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/lib/gui_qt.py b/lib/gui_qt.py index 7b89ab1fb..c619df375 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1380,22 +1380,6 @@ class ElectrumWindow(QMainWindow): hbox.addWidget(radio2) vbox.addLayout(hbox) - - hbox = QHBoxLayout() - proxy_mode = QComboBox() - proxy_host = QLineEdit() - proxy_port = QLineEdit() - proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) - proxy_mode.setCurrentIndex(proxy_mode.findText(str(interface.proxy["mode"]).upper())) - proxy_host.setText(interface.proxy["host"]) - proxy_port.setText(interface.proxy["port"]) - hbox.addWidget(QLabel(_('Proxy') + ':')) - hbox.addWidget(proxy_mode) - hbox.addWidget(proxy_host) - hbox.addWidget(proxy_port) - vbox.addLayout(hbox) - - hbox = QHBoxLayout() if wallet.interface.servers: label = _('Active Servers') @@ -1429,8 +1413,7 @@ class ElectrumWindow(QMainWindow): server = unicode( host_line.text() ) try: - proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } - wallet.set_server(server, proxy) + wallet.set_server(server) except: QMessageBox.information(None, _('Error'), 'error', _('OK')) if parent == None: diff --git a/lib/interface.py b/lib/interface.py index 83722de9a..185a2d47e 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -29,7 +29,7 @@ DEFAULT_SERVERS = [ 'ecdsa.org:50001:t', 'uncle-enzo.info:50001:t', 'electrum.bytesized-hosting.com:50001:t'] # list of default servers -proxy_modes = ['none', 'socks4', 'socks5', 'http' ] +proxy_modes = ['off', 'socks4', 'socks5', 'http' ] def replace_keys(obj, old_key, new_key): if isinstance(obj, dict): @@ -192,7 +192,7 @@ class HttpStratumInterface(PollingInterface): def send(self, messages): import urllib2, json, time, cookielib - if self.proxy["mode"] != "none": + if self.proxy["mode"] != "off": import socks socks.setdefaultproxy(proxy_modes.index(self.proxy["mode"]), self.proxy["host"], int(self.proxy["port"]) ) socks.wrapmodule(urllib2) @@ -260,7 +260,7 @@ class TcpStratumInterface(Interface): def init_socket(self): global proxy_modes - if self.proxy["mode"] == "none": + if self.proxy["mode"] == "off": self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) else: import socks @@ -460,7 +460,6 @@ class WalletSynchronizer(threading.Thread): if self.loop: time.sleep(5) # Server has been changed. Copy callback for new interface. - self.proxy = self.interface.proxy self.init_interface() self.start_interface() continue diff --git a/lib/simple_config.py b/lib/simple_config.py index e33efd5b0..3ade03305 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -3,7 +3,7 @@ import os from util import user_dir class SimpleConfig: - default_options = {"gui": "lite", "proxy": { "mode": "none", "host":"localhost", "port":"8080" } } + default_options = {"gui": "lite", "proxy": { "mode": "off", "host":"localhost", "port":"8080" } } def set_key(self, key, value, save = True): self.config[key] = value diff --git a/lib/wallet.py b/lib/wallet.py index c6e41295a..80d285998 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -347,16 +347,15 @@ class Wallet: def is_up_to_date(self): return self.interface.responses.empty() and not self.interface.unanswered_requests - def set_server(self, server, proxy): + def set_server(self, server): # 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: + if server != self.server: self.server = server self.save() - self.interface.proxy = proxy self.interface.is_connected = False # this exits the polling loop self.interface.poke() From 09c90c0971394dc5e88c03b5de399af5a94db730 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Thu, 20 Sep 2012 16:55:28 +0700 Subject: [PATCH 06/13] Revert "Revert "Added proxy options to network dialog"" This reverts commit 6e0b3620d23da54a0ea9a43cf568432bacb0852a. --- lib/gui_qt.py | 19 ++++++++++++++++++- lib/interface.py | 7 ++++--- lib/simple_config.py | 2 +- lib/wallet.py | 5 +++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/gui_qt.py b/lib/gui_qt.py index c619df375..7b89ab1fb 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1380,6 +1380,22 @@ class ElectrumWindow(QMainWindow): hbox.addWidget(radio2) vbox.addLayout(hbox) + + hbox = QHBoxLayout() + proxy_mode = QComboBox() + proxy_host = QLineEdit() + proxy_port = QLineEdit() + proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) + proxy_mode.setCurrentIndex(proxy_mode.findText(str(interface.proxy["mode"]).upper())) + proxy_host.setText(interface.proxy["host"]) + proxy_port.setText(interface.proxy["port"]) + hbox.addWidget(QLabel(_('Proxy') + ':')) + hbox.addWidget(proxy_mode) + hbox.addWidget(proxy_host) + hbox.addWidget(proxy_port) + vbox.addLayout(hbox) + + hbox = QHBoxLayout() if wallet.interface.servers: label = _('Active Servers') @@ -1413,7 +1429,8 @@ class ElectrumWindow(QMainWindow): server = unicode( host_line.text() ) try: - wallet.set_server(server) + proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } + wallet.set_server(server, proxy) except: QMessageBox.information(None, _('Error'), 'error', _('OK')) if parent == None: diff --git a/lib/interface.py b/lib/interface.py index 185a2d47e..83722de9a 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -29,7 +29,7 @@ DEFAULT_SERVERS = [ 'ecdsa.org:50001:t', 'uncle-enzo.info:50001:t', 'electrum.bytesized-hosting.com:50001:t'] # list of default servers -proxy_modes = ['off', 'socks4', 'socks5', 'http' ] +proxy_modes = ['none', 'socks4', 'socks5', 'http' ] def replace_keys(obj, old_key, new_key): if isinstance(obj, dict): @@ -192,7 +192,7 @@ class HttpStratumInterface(PollingInterface): def send(self, messages): import urllib2, json, time, cookielib - if self.proxy["mode"] != "off": + if self.proxy["mode"] != "none": import socks socks.setdefaultproxy(proxy_modes.index(self.proxy["mode"]), self.proxy["host"], int(self.proxy["port"]) ) socks.wrapmodule(urllib2) @@ -260,7 +260,7 @@ class TcpStratumInterface(Interface): def init_socket(self): global proxy_modes - if self.proxy["mode"] == "off": + if self.proxy["mode"] == "none": self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) else: import socks @@ -460,6 +460,7 @@ class WalletSynchronizer(threading.Thread): if self.loop: time.sleep(5) # Server has been changed. Copy callback for new interface. + self.proxy = self.interface.proxy self.init_interface() self.start_interface() continue diff --git a/lib/simple_config.py b/lib/simple_config.py index 3ade03305..e33efd5b0 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -3,7 +3,7 @@ import os from util import user_dir class SimpleConfig: - default_options = {"gui": "lite", "proxy": { "mode": "off", "host":"localhost", "port":"8080" } } + default_options = {"gui": "lite", "proxy": { "mode": "none", "host":"localhost", "port":"8080" } } def set_key(self, key, value, save = True): self.config[key] = value diff --git a/lib/wallet.py b/lib/wallet.py index 80d285998..c6e41295a 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -347,15 +347,16 @@ class Wallet: def is_up_to_date(self): return self.interface.responses.empty() and not self.interface.unanswered_requests - def set_server(self, server): + def set_server(self, server, proxy): # 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: + 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() From 9c4023ba58e6e015f066e3b9c8e32f9d3102b1d8 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Thu, 20 Sep 2012 17:01:47 +0700 Subject: [PATCH 07/13] Revert "Fix for wallet restore when offline" This reverts commit af750d9363bd58eaccb60fcdf5635ab402c77001. --- electrum | 3 --- 1 file changed, 3 deletions(-) diff --git a/electrum b/electrum index ce5159a2c..fde255bc1 100755 --- a/electrum +++ b/electrum @@ -251,8 +251,6 @@ if __name__ == '__main__': print "Recovery successful" else: print_error("Warning: Found no history for this wallet") - else: - wallet.synchronize() wallet.fill_addressbook() wallet.save() print_error("Wallet saved in '" + wallet.path) @@ -415,7 +413,6 @@ if __name__ == '__main__': wallet.save() elif cmd in [ 'addresses']: - for addr in wallet.all_addresses(): if options.show_all or not wallet.is_change(addr): From 9121654e8a909ff9f23793c311d8ccc9b9bf08ca Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Thu, 20 Sep 2012 18:24:36 +0700 Subject: [PATCH 08/13] Added proxy settings save to config.json --- lib/gui_qt.py | 13 +++++++++---- lib/interface.py | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/gui_qt.py b/lib/gui_qt.py index 7b89ab1fb..93809a044 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -37,6 +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 decimal import Decimal @@ -1384,7 +1385,9 @@ class ElectrumWindow(QMainWindow): hbox = QHBoxLayout() proxy_mode = QComboBox() proxy_host = QLineEdit() + proxy_host.setFixedWidth(200) proxy_port = QLineEdit() + proxy_port.setFixedWidth(50) proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) proxy_mode.setCurrentIndex(proxy_mode.findText(str(interface.proxy["mode"]).upper())) proxy_host.setText(interface.proxy["host"]) @@ -1429,10 +1432,12 @@ class ElectrumWindow(QMainWindow): server = unicode( host_line.text() ) try: - proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } - wallet.set_server(server, proxy) - except: - QMessageBox.information(None, _('Error'), 'error', _('OK')) + cfg = SimpleConfig() + cfg.config["proxy"] = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } + cfg.save_config() + wallet.set_server(server, cfg.config["proxy"]) + except Exception as err: + QMessageBox.information(None, _('Error'), str(err), _('OK')) if parent == None: sys.exit(1) else: diff --git a/lib/interface.py b/lib/interface.py index 83722de9a..1e04f2eee 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -342,7 +342,6 @@ class WalletSynchronizer(threading.Thread): self.proxy = proxy self.init_interface() self.servers_loaded_callback = servers_loaded_callback - def init_interface(self): try: From 8244768654123d03d2b050c17418aa9cab14e888 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Fri, 21 Sep 2012 04:48:03 +0700 Subject: [PATCH 09/13] Changed how load_config works so it always supports new config vars --- lib/simple_config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/simple_config.py b/lib/simple_config.py index e33efd5b0..a71c4810e 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -18,7 +18,9 @@ class SimpleConfig: f = open(self.config_file_path(), "r") file_contents = f.read() if file_contents: - self.config = json.loads(file_contents) + user_config = json.loads(file_contents) + for i in user_config: + self.config[i] = user_config[i] else: self.config = self.default_options self.save_config() @@ -29,10 +31,9 @@ class SimpleConfig: 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() - else: - self.config = self.default_options - self.save_config() + self.save_config() From 3007d95ceb2ef79412288f99b86a8db70138c700 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Fri, 21 Sep 2012 10:53:14 +0700 Subject: [PATCH 10/13] Added code to remember window position --- lib/gui_lite.py | 13 +++++++++++++ lib/gui_qt.py | 11 ++++++++++- lib/simple_config.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index 950598b14..bb267e365 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -5,6 +5,7 @@ from PyQt4.QtGui import * 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 @@ -231,6 +232,12 @@ class MiniWindow(QDialog): close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self) close_shortcut.activated.connect(self.close) + cfg = SimpleConfig() + g = cfg.config["winpos-lite"] + self.setGeometry(g[0], g[1], g[2], g[3]) + show_history.setChecked(cfg.config["history"]) + self.show_history(cfg.config["history"]) + self.setWindowIcon(QIcon(":electrum.png")) self.setWindowTitle("Electrum") self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint) @@ -247,6 +254,12 @@ class MiniWindow(QDialog): QDir.setCurrent(old_path) def closeEvent(self, event): + cfg = SimpleConfig() + g = self.geometry() + cfg.config["winpos-lite"] = [g.left(),g.top(),g.width(),g.height()] + cfg.config["history"] = self.history_list.isVisible() + cfg.save_config() + super(MiniWindow, self).closeEvent(event) qApp.quit() diff --git a/lib/gui_qt.py b/lib/gui_qt.py index c619df375..5fdd2487c 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -38,6 +38,7 @@ except: from wallet import format_satoshis import bmp, mnemonic, pyqrnative, qrscanner +from simple_config import SimpleConfig from decimal import Decimal @@ -202,7 +203,9 @@ class ElectrumWindow(QMainWindow): tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setCentralWidget(tabs) self.create_status_bar() - self.setGeometry(100,100,840,400) + cfg = SimpleConfig() + g = cfg.config["winpos-qt"] + self.setGeometry(g[0], g[1], g[2], g[3]) title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path if not self.wallet.seed: title += ' [seedless]' self.setWindowTitle( title ) @@ -1423,6 +1426,12 @@ class ElectrumWindow(QMainWindow): return True + def closeEvent(self, event): + cfg = SimpleConfig() + g = self.geometry() + cfg.config["winpos-qt"] = [g.left(),g.top(),g.width(),g.height()] + cfg.save_config() + event.accept() class ElectrumGui: diff --git a/lib/simple_config.py b/lib/simple_config.py index 06f78bdc1..582cd057f 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -3,7 +3,7 @@ import os from util import user_dir class SimpleConfig: - default_options = {"gui": "lite"} + default_options = {"gui": "lite", "winpos-qt": [100, 100, 840, 400], "winpos-lite": [4, 25, 351, 149], "history": False } def set_key(self, key, value, save = True): self.config[key] = value From 0ebbbb56b7485462263f3dc06e2597a4cc5e4be2 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Fri, 21 Sep 2012 13:20:24 +0700 Subject: [PATCH 11/13] Added socks module to setup --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a41ec8d31..67d79188a 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ setup(name = "Electrum", 'electrum.qrscanner', 'electrum.history_widget', 'electrum.simple_config', + 'electrum.socks', 'electrum.bmp', 'electrum.msqr', 'electrum.util', From ad24870a03e919c40b8da74c6415a710dbfec8d7 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Fri, 21 Sep 2012 15:10:43 +0700 Subject: [PATCH 12/13] small edit to config syntax --- lib/gui_lite.py | 4 ++-- lib/gui_qt.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index bb267e365..ef3260090 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -256,8 +256,8 @@ class MiniWindow(QDialog): def closeEvent(self, event): cfg = SimpleConfig() g = self.geometry() - cfg.config["winpos-lite"] = [g.left(),g.top(),g.width(),g.height()] - cfg.config["history"] = self.history_list.isVisible() + cfg.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()]) + cfg.set_key("history", self.history_list.isVisible()) cfg.save_config() super(MiniWindow, self).closeEvent(event) diff --git a/lib/gui_qt.py b/lib/gui_qt.py index 5fdd2487c..60bce0374 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1429,7 +1429,7 @@ class ElectrumWindow(QMainWindow): def closeEvent(self, event): cfg = SimpleConfig() g = self.geometry() - cfg.config["winpos-qt"] = [g.left(),g.top(),g.width(),g.height()] + cfg.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()]) cfg.save_config() event.accept() From 2bf2f76a2a7318910db5bf4bd8c060dc17c18794 Mon Sep 17 00:00:00 2001 From: bkkcoins Date: Fri, 21 Sep 2012 15:14:16 +0700 Subject: [PATCH 13/13] small edit to config syntax --- lib/gui_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gui_qt.py b/lib/gui_qt.py index 93809a044..6cb5c89b0 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1433,7 +1433,7 @@ class ElectrumWindow(QMainWindow): try: cfg = SimpleConfig() - cfg.config["proxy"] = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) } + cfg.set_key("proxy", { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }) cfg.save_config() wallet.set_server(server, cfg.config["proxy"]) except Exception as err: