From 0fc0bdd667559da3b6bb6b58018de56ecbdfa94f Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 12 Mar 2012 17:55:33 +0100 Subject: [PATCH] adding new protocol (draft) --- client/electrum | 13 +-- client/interface.py | 211 +++++++++++++++++++++++++--------------- client/remote_wallet.py | 4 +- client/wallet.py | 29 ++++-- 4 files changed, 159 insertions(+), 98 deletions(-) diff --git a/client/electrum b/client/electrum index 079259a10..8f24923e9 100755 --- a/client/electrum +++ b/client/electrum @@ -20,7 +20,6 @@ import re, sys, getpass from optparse import OptionParser from wallet import Wallet, SecretToASecret -from interface import Interface from decimal import Decimal @@ -43,11 +42,13 @@ if __name__ == '__main__': parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet") options, args = parser.parse_args() - interface = Interface() - wallet = Wallet(interface) + wallet = Wallet() wallet.set_path(options.wallet_path) + wallet.read() wallet.remote_url = options.remote_url + interface = wallet.interface + if len(args)==0: url = None cmd = 'gui' @@ -71,7 +72,7 @@ if __name__ == '__main__': gui = gui.ElectrumGui(wallet) try: - found = wallet.read() + found = wallet.file_exists if not found: found = gui.restore_or_create() except BaseException, e: @@ -90,14 +91,14 @@ if __name__ == '__main__': if cmd not in known_commands: cmd = 'help' - if not wallet.read() and cmd not in ['help','create','restore']: + if not wallet.file_exists and cmd not in ['help','create','restore']: print "Wallet file not found." print "Type 'electrum.py create' to create a new wallet, or provide a path to a wallet with the -d option" sys.exit(0) if cmd in ['create', 'restore']: import mnemonic - if wallet.read(): + if wallet.file_exists: print "remove the existing wallet first!" sys.exit(0) password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):") diff --git a/client/interface.py b/client/interface.py index 72184d7c2..9440a816b 100644 --- a/client/interface.py +++ b/client/interface.py @@ -20,7 +20,7 @@ import random, socket, ast -import thread, traceback, sys, time +import thread, traceback, sys, time, json DEFAULT_TIMEOUT=5 @@ -32,22 +32,49 @@ class Interface: self.rtime = 0 self.blocks = 0 self.message = '' - self.set_port(50000) self.is_connected = False self.was_updated = True # fixme: use a semaphore self.was_polled = False # True after the first poll - def set_port(self, port_number): - self.port = port_number - if self.use_http(): - self.handler = self.http_json_handler - else: - self.handler = self.native_handler + def send_tx(self, data): + out = self.handler('blockchain.transaction.broadcast', data ) + return out + + def retrieve_history(self, address): + out = self.handler('blockchain.address.get_history', address ) + return out + + def get_servers(self): + thread.start_new_thread(self.update_servers_thread, ()) + + def set_server(self, host, port): + if host!= self.host or port!=self.port: + self.host = host + self.port = port + self.is_connected = False + + def update_servers_thread(self): + pass + + +class NativeInterface(Interface): + """This is the original Electrum protocol. It uses polling, and a non-persistent tcp connection""" - def use_http(self): - return self.port in [80,81,8080,8081] + def __init__(self, host=None, port=50000): + Interface.__init__(self) + if host: self.host = host + self.port = port - def native_handler(self, method, params = ''): + def new_session(self, addresses, version): + self.was_polled = False + out = self.handler('session.new', [ version, addresses ] ) + self.session_id, self.message = ast.literal_eval( out ) + + def update_session(self, addresses): + out = self.handler('session.update', [ self.session_id, addresses ] ) + return out + + def handler(self, method, params = ''): import time cmds = {'session.new':'new_session', 'peers':'peers', @@ -76,69 +103,8 @@ class Interface: out = ast.literal_eval( out ) return out - def http_json_handler(self, method, params = []): - import urllib2, json, time - if type(params) != type([]): params = [ params ] - t1 = time.time() - data = { 'method':method, 'id':'jsonrpc', 'params':params } - data_json = json.dumps(data) - host = 'http://%s:%d'%( self.host if cmd!='peers' else self.peers_server, self.port ) - req = urllib2.Request(host, data_json, {'content-type': 'application/json'}) - response_stream = urllib2.urlopen(req) - response = json.loads( response_stream.read() ) - out = response.get('result') - if not out: - print response - self.rtime = time.time() - t1 - self.is_connected = True - return out - - def send_tx(self, data): - out = self.handler('blockchain.transaction.broadcast', data ) - return out - - def retrieve_history(self, address): - out = self.handler('blockchain.address.get_history', address ) - return out - - def poll(self): - out = self.handler('session.poll', self.session_id ) - blocks, changed_addr = ast.literal_eval( out ) - if blocks == -1: raise BaseException("session not found") - self.blocks = int(blocks) - if changed_addr: self.was_updated = True - self.was_polled = True - return changed_addr - - def new_session(self, addresses, version): - self.was_polled = False - out = self.handler('session.new', [ version, addresses ] ) - self.session_id, self.message = ast.literal_eval( out ) - - def update_session(self, addresses): - out = self.handler('session.update', [ self.session_id, addresses ] ) - return out - - def update_servers_thread(self): - # if my server is not reachable, I should get the list from one of the default servers - # requesting servers could be an independent process - while True: - for server in self.default_servers: - try: - self.peers_server = server - out = self.handler('peers') - self.servers = map( lambda x:x[1], out ) - # print "Received server list from %s" % self.peers_server, out - break - except socket.timeout: - continue - except socket.error: - continue - - time.sleep(5*60) - def poll_interval(self): - return 15 if self.use_http() else 5 + return 5 def update_wallet(self, wallet): is_new = False @@ -158,6 +124,15 @@ class Interface: else: return False + def poll(self): + out = self.handler('session.poll', self.session_id ) + blocks, changed_addr = ast.literal_eval( out ) + if blocks == -1: raise BaseException("session not found") + self.blocks = int(blocks) + if changed_addr: self.was_updated = True + self.was_polled = True + return changed_addr + def update_wallet_thread(self, wallet): while True: try: @@ -195,15 +170,89 @@ class Interface: traceback.print_exc(file=sys.stdout) break - def start(self, wallet): thread.start_new_thread(self.update_wallet_thread, (wallet,)) - def get_servers(self): - thread.start_new_thread(self.update_servers_thread, ()) + def update_servers_thread(self): + # if my server is not reachable, I should get the list from one of the default servers + # requesting servers could be an independent process + while True: + for server in self.default_servers: + try: + self.peers_server = server + out = self.handler('peers') + self.servers = map( lambda x:x[1], out ) + # print "Received server list from %s" % self.peers_server, out + break + except socket.timeout: + continue + except socket.error: + continue + except: + traceback.print_exc(file=sys.stdout) - def set_server(self, host, port): - if host!= self.host or port!=self.port: - self.host = host - self.set_port( port ) - self.is_connected = False + time.sleep(5*60) + + + + +class TCPInterface(Interface): + """json-rpc over TCP""" + + def __init__(self, host=None, port=50001): + Interface.__init__(self) + if host: self.host = host + self.port = 50001 + self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) + self.s.connect(( self.host, self.port)) + + def send(self, cmd, params = []): + request = json.dumps( { 'method':cmd, 'params':params } ) + self.s.send( request + '\n' ) + + def listen_thread(self, wallet): + out = '' + while True: + msg = self.s.recv(1024) + out += msg + s = out.find('\n') + if s!=-1: + c = out[0:s] + out = out[s+1:] + c = json.loads(c) + cmd = c.get('method') + if cmd == 'server.banner': + self.message = c.get('result') + else: + print "received message:", c + + def start(self, wallet): + thread.start_new_thread(self.listen_thread, (wallet,)) + self.send('client.version', wallet.electrum_version) + self.send('server.banner', None) + for address in wallet.all_addresses(): + self.send('blockchain.address.subscribe', address) + + + +class HttpInterface(Interface): + + def __init__(self): + self.port = 8081 + + def handler(self, method, params = []): + import urllib2, json, time + if type(params) != type([]): params = [ params ] + t1 = time.time() + data = { 'method':method, 'id':'jsonrpc', 'params':params } + data_json = json.dumps(data) + host = 'http://%s:%d'%( self.host if cmd!='peers' else self.peers_server, self.port ) + req = urllib2.Request(host, data_json, {'content-type': 'application/json'}) + response_stream = urllib2.urlopen(req) + response = json.loads( response_stream.read() ) + out = response.get('result') + if not out: + print response + self.rtime = time.time() - t1 + self.is_connected = True + return out diff --git a/client/remote_wallet.py b/client/remote_wallet.py index e0ea3fc9c..5e06dc04e 100644 --- a/client/remote_wallet.py +++ b/client/remote_wallet.py @@ -21,7 +21,6 @@ import time, thread, sys, socket # see http://code.google.com/p/jsonrpclib/ import jsonrpclib from wallet import Wallet -from interface import Interface """ Simple wallet daemon for webservers. @@ -41,8 +40,7 @@ port = 8444 wallet_path = 'wallet_path' username = 'foo' password = 'bar' -interface = Interface() -wallet = Wallet(interface) +wallet = Wallet() stopping = False diff --git a/client/wallet.py b/client/wallet.py index 0c72b17c6..e15435fa6 100644 --- a/client/wallet.py +++ b/client/wallet.py @@ -234,7 +234,7 @@ from version import ELECTRUM_VERSION, SEED_VERSION class Wallet: - def __init__(self, interface): + def __init__(self): self.electrum_version = ELECTRUM_VERSION self.seed_version = SEED_VERSION @@ -262,7 +262,6 @@ class Wallet: self.tx_history = {} self.imported_keys = {} - self.interface = interface self.remote_url = None @@ -541,23 +540,27 @@ class Wallet: f.close() def read(self): + from interface import NativeInterface, HttpInterface,TCPInterface + upgrade_msg = """This wallet seed is deprecated. Please run upgrade.py for a diagnostic.""" + self.file_exists = False try: f = open(self.path,"r") data = f.read() f.close() except: - return False + self.interface = NativeInterface() + return try: d = ast.literal_eval( data ) 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.fee = int( d.get('fee') ) - self.interface.host = d.get('host') - self.interface.set_port( d.get('port') ) - self.interface.blocks = d.get('blocks') self.seed = d.get('seed') + host = d.get('host') + port = d.get('port') + blocks = d.get('blocks') self.addresses = d.get('addresses') self.change_addresses = d.get('change_addresses') self.status = d.get('status') @@ -569,7 +572,7 @@ class Wallet: self.authorities = d.get('authorities',{}) self.receipts = d.get('receipts',{}) except: - raise BaseException(upgrade_msg) + raise BaseException("cannot read wallet file") self.update_tx_history() @@ -578,7 +581,17 @@ class Wallet: if self.remote_url: assert self.master_public_key.encode('hex') == self.get_remote_mpk() - return True + self.file_exists = True + + if port == 50000: + self.interface = NativeInterface(host,port) + elif port == 50001: + self.interface = TCPInterface(host,port) + elif port in [80,8080,81,8181]: + self.interface = HttpInterface(host,port) + else: + raise BaseException("unknown protocol: %d"%port) + def get_new_address(self): n = 0