block_number should return -1 if we know the DB is not up to date
[electrum-server.git] / server.py
index 59c3d0a..973f932 100755 (executable)
--- a/server.py
+++ b/server.py
@@ -27,13 +27,20 @@ Todo:
 
 import time, socket, operator, thread, ast, sys,re
 import psycopg2, binascii
-import bitcoinrpc
 
 from Abe.abe import hash_to_address, decode_check_address
 from Abe.DataStore import DataStore as Datastore_class
 from Abe import DataStore, readconf, BCDataStream,  deserialize, util, base58
 
 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
@@ -57,6 +64,7 @@ except:
     print "Could not read electrum.conf. I will use the default values."
 
 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'))
 
 stopping = False
 block_number = -1
@@ -317,14 +325,33 @@ 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):
-    import bitcoinrpc
-    conn = bitcoinrpc.connect_to_local()
-    try:
-        v = conn.importtransaction(tx)
-    except:
-        v = "error: transaction rejected by memorypool"
-    return v
+    postdata = dumps({"method": 'importtransaction', 'params': [tx], 'id':'jsonrpc'})
+    respdata = urllib.urlopen(bitcoind_url, postdata).read()
+    r = loads(respdata)
+    if r['error'] != None:
+        out = "error: transaction rejected by memorypool"
+    else:
+        out = r['result']
+    return out
+
+
 
 def random_string(N):
     import random, string
@@ -361,10 +388,10 @@ 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, ipaddr
+        print time.asctime(), "session not found", session_id
         out = repr( (-1, {}))
     else:
         t1 = time.time()
@@ -400,12 +427,9 @@ 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') ) )
@@ -413,13 +437,11 @@ def new_session(addresses, version, ipaddr):
     return out
 
 def update_session(session_id,addresses):
-    print time.strftime("[%d/%m/%Y-%H:%M:%S]"), "update session", ipaddr, addresses[0] if addresses else addresses, len(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)
@@ -466,6 +488,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
 
@@ -480,7 +504,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:
@@ -488,9 +513,9 @@ def do_command(cmd, data, ipaddr):
         except:
             print "error"
             return None
+        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
         print "data",data
@@ -498,7 +523,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
@@ -506,7 +531,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:
@@ -519,7 +544,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:
@@ -539,7 +564,7 @@ def do_command(cmd, data, ipaddr):
         out = ''
             
     elif cmd=='poll': 
-        out = cmd_poll(data)
+        out = poll_session(data)
 
     elif cmd == 'h': 
         # history
@@ -551,7 +576,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)
@@ -570,13 +595,14 @@ def do_command(cmd, data, ipaddr):
 def memorypool_update(store):
     ds = BCDataStream.BCDataStream()
     store.mempool_keys = []
-    conn = bitcoinrpc.connect_to_local()
-    try:
-        v = conn.getmemorypool()
-    except:
-        print "cannot contact bitcoin daemon"
+
+    postdata = dumps({"method": 'getmemorypool', 'params': [], 'id':'jsonrpc'})
+    respdata = urllib.urlopen(bitcoind_url, postdata).read()
+    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'))
@@ -600,7 +626,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)
             
 
@@ -649,15 +674,21 @@ 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))
-    server.register_function(store.get_history, 'history')
+    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')
     server.register_function(lambda : block_number, 'blocks')
     server.register_function(clear_cache, 'clear_cache')
     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()
 
 
@@ -668,7 +699,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)
@@ -681,7 +712,9 @@ if __name__ == '__main__':
         elif cmd == 'get_cache':
             out = server.get_cache(password,sys.argv[2])
         elif cmd == 'h':
-            out = server.history(sys.argv[2])
+            out = server.blockchain.address.get_history(sys.argv[2])
+        elif cmd == 'tx':
+            out = server.blockchain.transaction.broadcast(sys.argv[2])
         elif cmd == 'b':
             out = server.blocks()
         print out
@@ -716,9 +749,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 = -1 
         except:
             traceback.print_exc(file=sys.stdout)
+            block_number = -1
+        finally:
+            dblock.release()
         time.sleep(10)
 
     print "server stopped"