URI: 
       tadding new protocol (draft) - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 0fc0bdd667559da3b6bb6b58018de56ecbdfa94f
   DIR parent 6d51e3eae867e65c35772a647a40535fc088bb5c
  HTML Author: thomasv <thomasv@gitorious>
       Date:   Mon, 12 Mar 2012 17:55:33 +0100
       
       adding new protocol (draft)
       
       Diffstat:
         M client/electrum                     |      13 +++++++------
         M client/interface.py                 |     211 +++++++++++++++++++------------
         M client/remote_wallet.py             |       4 +---
         M client/wallet.py                    |      29 +++++++++++++++++++++--------
       
       4 files changed, 159 insertions(+), 98 deletions(-)
       ---
   DIR diff --git a/client/electrum b/client/electrum
       t@@ -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
        
        
       t@@ -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'
       t@@ -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:
       t@@ -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):")
   DIR diff --git a/client/interface.py b/client/interface.py
       t@@ -20,7 +20,7 @@
        import random, socket, ast
        
                
       -import thread, traceback, sys, time
       +import thread, traceback, sys, time, json
        
        DEFAULT_TIMEOUT=5
        
       t@@ -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',
       t@@ -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
       t@@ -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:
       t@@ -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
   DIR diff --git a/client/remote_wallet.py b/client/remote_wallet.py
       t@@ -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.
       t@@ -41,8 +40,7 @@ port = 8444
        wallet_path = 'wallet_path'
        username = 'foo'
        password = 'bar'
       -interface = Interface()
       -wallet = Wallet(interface)
       +wallet = Wallet()
        stopping = False
        
        
   DIR diff --git a/client/wallet.py b/client/wallet.py
       t@@ -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
       t@@ -262,7 +262,6 @@ class Wallet:
                self.tx_history = {}
        
                self.imported_keys = {}
       -        self.interface = interface
                self.remote_url = None
        
        
       t@@ -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')
       t@@ -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()
        
       t@@ -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