version 0.40a
[electrum-server.git] / server.py
index 1071558..9b2fc49 100755 (executable)
--- a/server.py
+++ b/server.py
@@ -36,6 +36,12 @@ import ConfigParser
 from json import dumps, loads
 import urllib
 
+# we need to import electrum
+sys.path.append('../client/')
+from wallet import Wallet
+from interface import Interface
+
+
 config = ConfigParser.ConfigParser()
 # set some defaults, which will be overwritten by the config file
 config.add_section('server')
@@ -57,6 +63,13 @@ try:
 except:
     print "Could not read electrum.conf. I will use the default values."
 
+try:
+    f = open('/etc/electrum.banner','r')
+    config.set('server','banner', f.read())
+    f.close()
+except:
+    pass
+
 password = config.get('server','password')
 bitcoind_url = 'http://%s:%s@%s:%s/' % ( config.get('bitcoind','user'), config.get('bitcoind','password'), config.get('bitcoind','host'), config.get('bitcoind','port'))
 
@@ -319,15 +332,31 @@ class MyStore(Datastore_class):
 
 
 
+class Direct_Interface(Interface):
+    def __init__(self):
+        pass
+
+    def handler(self, method, params = ''):
+        cmds = {'session.new':new_session,
+                'session.poll':poll_session,
+                'session.update':update_session,
+                'blockchain.transaction.broadcast':send_tx,
+                'blockchain.address.get_history':store.get_history
+                }
+        func = cmds[method]
+        return func( params )
+
+
 
 def send_tx(tx):
     postdata = dumps({"method": 'importtransaction', 'params': [tx], 'id':'jsonrpc'})
     respdata = urllib.urlopen(bitcoind_url, postdata).read()
-    try:
-        v = loads(respdata)['result']
-    except:
-        v = "error: transaction rejected by memorypool"
-    return v
+    r = loads(respdata)
+    if r['error'] != None:
+        out = "error: transaction rejected by memorypool"
+    else:
+        out = r['result']
+    return out
 
 
 
@@ -366,7 +395,7 @@ def get_cache(pw,addr):
         return 'wrong password'
 
 
-def cmd_poll(session_id):
+def poll_session(session_id):
     session = sessions.get(session_id)
     if session is None:
         print time.asctime(), "session not found", session_id
@@ -405,26 +434,21 @@ def cmd_poll(session_id):
         return out
 
 
-def new_session(addresses, version, ipaddr):
+def new_session(version, addresses):
     session_id = random_string(10)
-
-    print time.strftime("[%d/%m/%Y-%H:%M:%S]"), "new session", ipaddr, addresses[0] if addresses else addresses, len(addresses), version
-
-    sessions[session_id] = { 'addresses':{}, 'version':version, 'ip':ipaddr }
+    sessions[session_id] = { 'addresses':{}, 'version':version }
     for a in addresses:
         sessions[session_id]['addresses'][a] = ''
     out = repr( (session_id, config.get('server','banner').replace('\\n','\n') ) )
     sessions[session_id]['last_time'] = time.time()
     return out
 
-def update_session(session_id,addresses,ipaddr):
-    print time.strftime("[%d/%m/%Y-%H:%M:%S]"), "update session", ipaddr, addresses[0] if addresses else addresses, len(addresses)
+def update_session(session_id,addresses):
     sessions[session_id]['addresses'] = {}
     for a in addresses:
         sessions[session_id]['addresses'][a] = ''
-    out = 'ok'
     sessions[session_id]['last_time'] = time.time()
-
+    return 'ok'
 
 def listen_thread(store):
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -433,7 +457,11 @@ def listen_thread(store):
     s.listen(1)
     while not stopping:
         conn, addr = s.accept()
-        thread.start_new_thread(client_thread, (addr, conn,))
+        try:
+            thread.start_new_thread(client_thread, (addr, conn,))
+        except:
+            # can't start new thread if there is no memory..
+            traceback.print_exc(file=sys.stdout)
 
 
 
@@ -471,6 +499,8 @@ def client_thread(ipaddr,conn):
 
 def do_command(cmd, data, ipaddr):
 
+    timestr = time.strftime("[%d/%m/%Y-%H:%M:%S]")
+
     if cmd=='b':
         out = "%d"%block_number
 
@@ -485,7 +515,8 @@ def do_command(cmd, data, ipaddr):
         except:
             print "error", data
             return None
-        out = new_session(addresses, version, ipaddr)
+        print timestr, "new session", ipaddr, addresses[0] if addresses else addresses, len(addresses), version
+        out = new_session(version, addresses)
 
     elif cmd=='update_session':
         try:
@@ -493,8 +524,8 @@ def do_command(cmd, data, ipaddr):
         except:
             print "error"
             return None
-        out = update_session(session_id,addresses,ipaddr)
-
+        print timestr, "update session", ipaddr, addresses[0] if addresses else addresses, len(addresses)
+        out = update_session(session_id,addresses)
 
     elif cmd == 'bccapi_login':
         import electrum
@@ -503,7 +534,7 @@ def do_command(cmd, data, ipaddr):
         master_public_key = k.decode('hex') # todo: sanitize. no need to decode twice...
         print master_public_key
         wallet_id = random_string(10)
-        w = electrum.Wallet()
+        w = Wallet( Direct_Interface() )
         w.master_public_key = master_public_key.decode('hex')
         w.synchronize()
         wallets[wallet_id] = w
@@ -511,7 +542,7 @@ def do_command(cmd, data, ipaddr):
         print "wallets", wallets
 
     elif cmd == 'bccapi_getAccountInfo':
-        from electrum import int_to_hex
+        from wallet import int_to_hex
         v, wallet_id = ast.literal_eval(data)
         w = wallets.get(wallet_id)
         if w is not None:
@@ -524,7 +555,7 @@ def do_command(cmd, data, ipaddr):
             out = "error"
 
     elif cmd == 'bccapi_getAccountStatement':
-        from electrum import int_to_hex
+        from wallet import int_to_hex
         v, wallet_id = ast.literal_eval(data)
         w = wallets.get(wallet_id)
         if w is not None:
@@ -544,7 +575,7 @@ def do_command(cmd, data, ipaddr):
         out = ''
             
     elif cmd=='poll': 
-        out = cmd_poll(data)
+        out = poll_session(data)
 
     elif cmd == 'h': 
         # history
@@ -556,7 +587,7 @@ def do_command(cmd, data, ipaddr):
 
     elif cmd =='tx':
         out = send_tx(data)
-        print "sent tx:", out
+        print timestr, "sent tx:", ipaddr, out
 
     elif cmd == 'stop':
         out = cmd_stop(data)
@@ -578,10 +609,11 @@ def memorypool_update(store):
 
     postdata = dumps({"method": 'getmemorypool', 'params': [], 'id':'jsonrpc'})
     respdata = urllib.urlopen(bitcoind_url, postdata).read()
-    v = loads(respdata)['result']
-
+    r = loads(respdata)
+    if r['error'] != None:
+        return
 
-    v = v['transactions']
+    v = r['result'].get('transactions')
     for hextx in v:
         ds.clear()
         ds.write(hextx.decode('hex'))
@@ -605,7 +637,6 @@ def clean_session_thread():
         for k,s in sessions.items():
             t0 = s['last_time']
             if t - t0 > 5*60:
-                print time.strftime("[%d/%m/%Y-%H:%M:%S]"), "end session", s['ip']
                 sessions.pop(k)
             
 
@@ -654,8 +685,10 @@ def irc_thread():
 
 def jsonrpc_thread(store):
     # see http://code.google.com/p/jsonrpclib/
+    from SocketServer import ThreadingMixIn
     from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
-    server = SimpleJSONRPCServer(('localhost', 8080))
+    class SimpleThreadedJSONRPCServer(ThreadingMixIn, SimpleJSONRPCServer): pass
+    server = SimpleThreadedJSONRPCServer(( config.get('server','host'), 8081))
     server.register_function(lambda : peer_list.values(), 'peers')
     server.register_function(cmd_stop, 'stop')
     server.register_function(cmd_load, 'load')
@@ -664,6 +697,9 @@ def jsonrpc_thread(store):
     server.register_function(get_cache, 'get_cache')
     server.register_function(send_tx, 'blockchain.transaction.broadcast')
     server.register_function(store.get_history, 'blockchain.address.get_history')
+    server.register_function(new_session, 'session.new')
+    server.register_function(update_session, 'session.update')
+    server.register_function(poll_session, 'session.poll')
     server.serve_forever()
 
 
@@ -674,7 +710,7 @@ if __name__ == '__main__':
 
     if len(sys.argv)>1:
         import jsonrpclib
-        server = jsonrpclib.Server('http://localhost:8080')
+        server = jsonrpclib.Server('http://%s:8081'%config.get('server','host'))
         cmd = sys.argv[1]
         if cmd == 'load':
             out = server.load(password)
@@ -692,6 +728,8 @@ if __name__ == '__main__':
             out = server.blockchain.transaction.broadcast(sys.argv[2])
         elif cmd == 'b':
             out = server.blocks()
+        else:
+            out = "Unknown command: '%s'" % cmd
         print out
         sys.exit(0)
 
@@ -724,9 +762,14 @@ if __name__ == '__main__':
             store.catch_up()
             memorypool_update(store)
             block_number = store.get_block_number(1)
-            dblock.release()
+        except IOError:
+            print "IOError: cannot reach bitcoind"
+            block_number = 0
         except:
             traceback.print_exc(file=sys.stdout)
+            block_number = 0
+        finally:
+            dblock.release()
         time.sleep(10)
 
     print "server stopped"