handle signals
[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(params):
86     cmd = params[0]
87     import xmlrpclib
88     server = xmlrpclib.ServerProxy('http://localhost:8000')
89     func = getattr(server, cmd)
90     try:
91         r = func(*params[1:])
92     except socket.error:
93         print "server not running"
94         sys.exit(1)
95
96     if cmd == 'info':
97         now = time.time()
98         print 'type           address         sub  version  time'
99         for item in r:
100             print '%4s   %21s   %3s  %7s  %.2f' % (item.get('name'),
101                                                    item.get('address'),
102                                                    item.get('subscriptions'),
103                                                    item.get('version'),
104                                                    (now - item.get('time')),
105                                                    )
106     else:
107         print r
108
109
110 def cmd_info():
111     return map(lambda s: {"time": s.time,
112                           "name": s.name,
113                           "address": s.address,
114                           "version": s.version,
115                           "subscriptions": len(s.subscriptions)},
116                dispatcher.request_dispatcher.get_sessions())
117
118 def cmd_debug(s):
119     if s:
120         from guppy import hpy
121         h = hpy()
122         bp = dispatcher.request_dispatcher.processors['blockchain']
123         try:
124             result = str(eval(s))
125         except:
126             result = "error"
127         return result
128
129
130 if __name__ == '__main__':
131     config = create_config()
132     password = config.get('server', 'password')
133     host = config.get('server', 'host')
134     stratum_tcp_port = config.getint('server', 'stratum_tcp_port')
135     stratum_http_port = config.getint('server', 'stratum_http_port')
136     stratum_tcp_ssl_port = config.getint('server', 'stratum_tcp_ssl_port')
137     stratum_http_ssl_port = config.getint('server', 'stratum_http_ssl_port')
138     ssl_certfile = config.get('server', 'ssl_certfile')
139     ssl_keyfile = config.get('server', 'ssl_keyfile')
140
141     if stratum_tcp_ssl_port or stratum_http_ssl_port:
142         assert ssl_certfile and ssl_keyfile
143
144     if len(sys.argv) > 1:
145         run_rpc_command(sys.argv[1:])
146         sys.exit(0)
147
148     try:
149         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
150         s.connect((host, stratum_tcp_port))
151         s.close()
152         is_running = True
153     except:
154         is_running = False
155
156     if is_running:
157         print "server already running"
158         sys.exit(1)
159
160
161     from processor import Dispatcher, print_log
162     from backends.irc import ServerProcessor
163     from transports.stratum_tcp import TcpServer
164     from transports.stratum_http import HttpServer
165
166     backend_name = config.get('server', 'backend')
167     if backend_name == 'libbitcoin':
168         from backends.libbitcoin import BlockchainProcessor
169     elif backend_name == 'leveldb':
170         from backends.bitcoind import BlockchainProcessor
171     else:
172         print "Unknown backend '%s' specified\n" % backend_name
173         sys.exit(1)
174
175     print "\n\n\n\n\n"
176     print_log("Starting Electrum server on", host)
177
178     # Create hub
179     dispatcher = Dispatcher(config)
180     shared = dispatcher.shared
181
182     # handle termination signals
183     import signal
184     def handler(signum = None, frame = None):
185         print_log('Signal handler called with signal', signum)
186         shared.stop()
187     for sig in [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT]:
188         signal.signal(sig, handler)
189
190
191     # Create and register processors
192     chain_proc = BlockchainProcessor(config, shared)
193     dispatcher.register('blockchain', chain_proc)
194
195     server_proc = ServerProcessor(config)
196     dispatcher.register('server', server_proc)
197
198     transports = []
199     # Create various transports we need
200     if stratum_tcp_port:
201         tcp_server = TcpServer(dispatcher, host, stratum_tcp_port, False, None, None)
202         transports.append(tcp_server)
203
204     if stratum_tcp_ssl_port:
205         tcp_server = TcpServer(dispatcher, host, stratum_tcp_ssl_port, True, ssl_certfile, ssl_keyfile)
206         transports.append(tcp_server)
207
208     if stratum_http_port:
209         http_server = HttpServer(dispatcher, host, stratum_http_port, False, None, None)
210         transports.append(http_server)
211
212     if stratum_http_ssl_port:
213         http_server = HttpServer(dispatcher, host, stratum_http_ssl_port, True, ssl_certfile, ssl_keyfile)
214         transports.append(http_server)
215
216     for server in transports:
217         server.start()
218
219     
220
221     from SimpleXMLRPCServer import SimpleXMLRPCServer
222     server = SimpleXMLRPCServer(('localhost',8000), allow_none=True, logRequests=False)
223     server.register_function(lambda: os.getpid(), 'getpid')
224     server.register_function(shared.stop, 'stop')
225     server.register_function(cmd_info, 'info')
226     server.register_function(cmd_debug, 'debug')
227     server.socket.settimeout(1)
228  
229     while not shared.stopped():
230         try:
231             server.handle_request()
232         except socket.timeout:
233             continue
234         except:
235             shared.stop()
236
237     server_proc.join()
238     chain_proc.join()
239     print_log("Electrum Server stopped")