add a separate channel for server commands
[electrum-server.git] / server.py
1 #!/usr/bin/env python
2 # Copyright(C) 2012 thomasv@gitorious
3
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public
15 # License along with this program.  If not, see
16 # <http://www.gnu.org/licenses/agpl.html>.
17
18 import ConfigParser
19 import logging
20 import socket
21 import sys
22 import time
23 import threading
24 import traceback
25
26 import json
27 import os
28
29 logging.basicConfig()
30
31 if sys.maxsize <= 2**32:
32     print "Warning: it looks like you are using a 32bit system. You may experience crashes caused by mmap"
33
34
35 def attempt_read_config(config, filename):
36     try:
37         with open(filename, 'r') as f:
38             config.readfp(f)
39     except IOError:
40         pass
41
42
43 def create_config():
44     config = ConfigParser.ConfigParser()
45     # set some defaults, which will be overwritten by the config file
46     config.add_section('server')
47     config.set('server', 'banner', 'Welcome to Electrum!')
48     config.set('server', 'host', 'localhost')
49     config.set('server', 'report_host', '')
50     config.set('server', 'stratum_tcp_port', '50001')
51     config.set('server', 'stratum_http_port', '8081')
52     config.set('server', 'stratum_tcp_ssl_port', '')
53     config.set('server', 'stratum_http_ssl_port', '')
54     config.set('server', 'report_stratum_tcp_port', '')
55     config.set('server', 'report_stratum_http_port', '')
56     config.set('server', 'report_stratum_tcp_ssl_port', '')
57     config.set('server', 'report_stratum_http_ssl_port', '')
58     config.set('server', 'ssl_certfile', '')
59     config.set('server', 'ssl_keyfile', '')
60     config.set('server', 'password', '')
61     config.set('server', 'irc', 'no')
62     config.set('server', 'irc_nick', '')
63     config.set('server', 'coin', '')
64     config.set('server', 'datadir', '')
65
66     # use leveldb as default
67     config.set('server', 'backend', 'leveldb')
68     config.add_section('leveldb')
69     config.set('leveldb', 'path', '/dev/shm/electrum_db')
70     config.set('leveldb', 'pruning_limit', '100')
71
72     for path in ('/etc/', ''):
73         filename = path + 'electrum.conf'
74         attempt_read_config(config, filename)
75
76     try:
77         with open('/etc/electrum.banner', 'r') as f:
78             config.set('server', 'banner', f.read())
79     except IOError:
80         pass
81
82     return config
83
84
85 def run_rpc_command(cmd, stratum_tcp_port):
86     import xmlrpclib
87     server = xmlrpclib.ServerProxy('http://localhost:8000')
88     func = getattr(server, cmd)
89     try:
90         r = func()
91     except socket.error:
92         print "server not running"
93         sys.exit(1)
94
95     if cmd == 'info':
96         now = time.time()
97         print 'type           address         sub  version  time'
98         for item in r:
99             print '%4s   %21s   %3s  %7s  %.2f' % (item.get('name'),
100                                                    item.get('address'),
101                                                    item.get('subscriptions'),
102                                                    item.get('version'),
103                                                    (now - item.get('time')),
104                                                    )
105     else:
106         print r
107
108
109 def cmd_info():
110     return map(lambda s: {"time": s.time,
111                           "name": s.name,
112                           "address": s.address,
113                           "version": s.version,
114                           "subscriptions": len(s.subscriptions)},
115                dispatcher.request_dispatcher.get_sessions())
116
117 def cmd_debug():
118     try:
119         s = request['params'][1]
120     except:
121         s = None
122
123     if s:
124         from guppy import hpy
125         h = hpy()
126         bp = dispatcher.request_dispatcher.processors['blockchain']
127         try:
128             result = str(eval(s))
129         except:
130             result = "error"
131         return result
132
133
134 if __name__ == '__main__':
135     config = create_config()
136     password = config.get('server', 'password')
137     host = config.get('server', 'host')
138     stratum_tcp_port = config.getint('server', 'stratum_tcp_port')
139     stratum_http_port = config.getint('server', 'stratum_http_port')
140     stratum_tcp_ssl_port = config.getint('server', 'stratum_tcp_ssl_port')
141     stratum_http_ssl_port = config.getint('server', 'stratum_http_ssl_port')
142     ssl_certfile = config.get('server', 'ssl_certfile')
143     ssl_keyfile = config.get('server', 'ssl_keyfile')
144
145     if stratum_tcp_ssl_port or stratum_http_ssl_port:
146         assert ssl_certfile and ssl_keyfile
147
148     if len(sys.argv) > 1:
149         run_rpc_command(sys.argv[1], stratum_tcp_port)
150         sys.exit(0)
151
152     try:
153         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
154         s.connect((host, stratum_tcp_port))
155         s.close()
156         is_running = True
157     except:
158         is_running = False
159
160     if is_running:
161         print "server already running"
162         sys.exit(1)
163
164
165     from processor import Dispatcher, print_log
166     from backends.irc import ServerProcessor
167     from transports.stratum_tcp import TcpServer
168     from transports.stratum_http import HttpServer
169
170     backend_name = config.get('server', 'backend')
171     if backend_name == 'libbitcoin':
172         from backends.libbitcoin import BlockchainProcessor
173     elif backend_name == 'leveldb':
174         from backends.bitcoind import BlockchainProcessor
175     else:
176         print "Unknown backend '%s' specified\n" % backend_name
177         sys.exit(1)
178
179     print "\n\n\n\n\n"
180     print_log("Starting Electrum server on", host)
181
182     # Create hub
183     dispatcher = Dispatcher(config)
184     shared = dispatcher.shared
185
186     # Create and register processors
187     chain_proc = BlockchainProcessor(config, shared)
188     dispatcher.register('blockchain', chain_proc)
189
190     server_proc = ServerProcessor(config)
191     dispatcher.register('server', server_proc)
192
193     transports = []
194     # Create various transports we need
195     if stratum_tcp_port:
196         tcp_server = TcpServer(dispatcher, host, stratum_tcp_port, False, None, None)
197         transports.append(tcp_server)
198
199     if stratum_tcp_ssl_port:
200         tcp_server = TcpServer(dispatcher, host, stratum_tcp_ssl_port, True, ssl_certfile, ssl_keyfile)
201         transports.append(tcp_server)
202
203     if stratum_http_port:
204         http_server = HttpServer(dispatcher, host, stratum_http_port, False, None, None)
205         transports.append(http_server)
206
207     if stratum_http_ssl_port:
208         http_server = HttpServer(dispatcher, host, stratum_http_ssl_port, True, ssl_certfile, ssl_keyfile)
209         transports.append(http_server)
210
211     for server in transports:
212         server.start()
213
214
215
216     from SimpleXMLRPCServer import SimpleXMLRPCServer
217     
218
219     server = SimpleXMLRPCServer(('localhost',8000), allow_none=True, logRequests=False)
220     server.register_function(lambda: os.getpid(), 'getpid')
221     server.register_function(shared.stop, 'stop')
222     server.register_function(cmd_info, 'info')
223     server.register_function(cmd_debug, 'debug')
224  
225     while not shared.stopped():
226         try:
227             server.handle_request()
228         except:
229             shared.stop()
230
231     server_proc.join()
232     chain_proc.join()
233     print_log("Electrum Server stopped")