NVC/PPC protocol changes support
[p2pool.git] / p2pool / main.py
1 from __future__ import division
2
3 import base64
4 import gc
5 import json
6 import os
7 import random
8 import sys
9 import time
10 import signal
11 import traceback
12 import urlparse
13
14 if '--iocp' in sys.argv:
15     from twisted.internet import iocpreactor
16     iocpreactor.install()
17 from twisted.internet import defer, reactor, protocol, task
18 from twisted.web import server
19 from twisted.python import log
20 from nattraverso import portmapper, ipdiscover
21
22 import bitcoin.p2p as bitcoin_p2p, bitcoin.data as bitcoin_data
23 from bitcoin import stratum, worker_interface, helper
24 from util import fixargparse, jsonrpc, variable, deferral, math, logging, switchprotocol
25 from . import networks, web, work
26 import p2pool, p2pool.data as p2pool_data, p2pool.node as p2pool_node
27
28 @defer.inlineCallbacks
29 def main(args, net, datadir_path, merged_urls, worker_endpoint):
30     try:
31         print 'p2pool (version %s)' % (p2pool.__version__,)
32         print
33         
34         @defer.inlineCallbacks
35         def connect_p2p():
36             # connect to bitcoind over bitcoin-p2p
37             print '''Testing bitcoind P2P connection to '%s:%s'...''' % (args.bitcoind_address, args.bitcoind_p2p_port)
38             factory = bitcoin_p2p.ClientFactory(net.PARENT)
39             reactor.connectTCP(args.bitcoind_address, args.bitcoind_p2p_port, factory)
40             yield factory.getProtocol() # waits until handshake is successful
41             print '    ...success!'
42             print
43             defer.returnValue(factory)
44         
45         if args.testnet: # establish p2p connection first if testnet so bitcoind can work without connections
46             factory = yield connect_p2p()
47         
48         # connect to bitcoind over JSON-RPC and do initial getmemorypool
49         url = '%s://%s:%i/' % ('https' if args.bitcoind_rpc_ssl else 'http', args.bitcoind_address, args.bitcoind_rpc_port)
50         print '''Testing bitcoind RPC connection to '%s' with username '%s'...''' % (url, args.bitcoind_rpc_username)
51         bitcoind = jsonrpc.HTTPProxy(url, dict(Authorization='Basic ' + base64.b64encode(args.bitcoind_rpc_username + ':' + args.bitcoind_rpc_password)), timeout=30)
52         yield helper.check(bitcoind, net)
53         temp_work = yield helper.getwork(bitcoind)
54         
55         bitcoind_warning_var = variable.Variable(None)
56         @defer.inlineCallbacks
57         def poll_warnings():
58             errors = (yield deferral.retry('Error while calling getmininginfo:')(bitcoind.rpc_getmininginfo)())['errors']
59             bitcoind_warning_var.set(errors if errors != '' else None)
60         yield poll_warnings()
61         task.LoopingCall(poll_warnings).start(20*60)
62         
63         print '    ...success!'
64         print '    Current block hash: %x' % (temp_work['previous_block'],)
65         print '    Current block height: %i' % (temp_work['height'] - 1,)
66         print
67         
68         if not args.testnet:
69             factory = yield connect_p2p()
70         
71         print 'Determining payout address...'
72         pubkey_path = os.path.join(datadir_path, 'cached_payout_pubkey')
73         
74         if os.path.exists(pubkey_path):
75             with open(pubkey_path, 'rb') as f:
76                 pubkey = f.read().strip('\r\n')
77             print '    Loaded cached pubkey, payout address: %s...' % (bitcoin_data.pubkey_to_address(pubkey.decode('hex'), net.PARENT),)
78         else:
79             pubkey = None
80
81         if pubkey is not None:
82                 res = yield deferral.retry('Error validating cached pubkey:', 5)(lambda: bitcoind.rpc_validatepubkey(pubkey))()
83                 if not res['isvalid'] or not res['ismine']:
84                     print '    Cached pubkey is either invalid or not controlled by local bitcoind!'
85                     address = None
86
87         if pubkey is None:
88             print '    Getting payout pubkey from bitcoind...'
89             pubkey = yield deferral.retry('Error getting payout pubkey from bitcoind:', 5)(lambda: bitcoind.rpc_getnewpubkey('p2pool'))()
90             
91             with open(pubkey_path, 'wb') as f:
92                 f.write(pubkey)
93
94         my_pubkey = pubkey.decode('hex')
95
96         address = bitcoin_data.pubkey_to_address(my_pubkey, net.PARENT)
97
98         my_pubkey_hash = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
99         print '    ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT)
100         print
101         
102         ss = p2pool_data.ShareStore(os.path.join(datadir_path, 'shares.'), net)
103         shares = {}
104         known_verified = set()
105         print "Loading shares..."
106         for i, (mode, contents) in enumerate(ss.get_shares()):
107             if mode == 'share':
108                 contents.time_seen = 0
109                 shares[contents.hash] = contents
110                 if len(shares) % 1000 == 0 and shares:
111                     print "    %i" % (len(shares),)
112             elif mode == 'verified_hash':
113                 known_verified.add(contents)
114             else:
115                 raise AssertionError()
116         print "    ...done loading %i shares (%i verified)!" % (len(shares), len(known_verified))
117         print
118         
119         
120         print 'Initializing work...'
121         
122         node = p2pool_node.Node(factory, bitcoind, shares.values(), known_verified, net)
123         yield node.start()
124         
125         for share_hash in shares:
126             if share_hash not in node.tracker.items:
127                 ss.forget_share(share_hash)
128         for share_hash in known_verified:
129             if share_hash not in node.tracker.verified.items:
130                 ss.forget_verified_share(share_hash)
131         del shares, known_verified
132         node.tracker.removed.watch(lambda share: ss.forget_share(share.hash))
133         node.tracker.verified.removed.watch(lambda share: ss.forget_verified_share(share.hash))
134         
135         def save_shares():
136             for share in node.tracker.get_chain(node.best_share_var.value, min(node.tracker.get_height(node.best_share_var.value), 2*net.CHAIN_LENGTH)):
137                 ss.add_share(share)
138                 if share.hash in node.tracker.verified.items:
139                     ss.add_verified_hash(share.hash)
140         task.LoopingCall(save_shares).start(60)
141         
142         print '    ...success!'
143         print
144         
145         
146         print 'Joining p2pool network using port %i...' % (args.p2pool_port,)
147         
148         @defer.inlineCallbacks
149         def parse(x):
150             if ':' in x:
151                 ip, port = x.split(':')
152                 defer.returnValue(((yield reactor.resolve(ip)), int(port)))
153             else:
154                 defer.returnValue(((yield reactor.resolve(x)), net.P2P_PORT))
155         
156         addrs = {}
157         if os.path.exists(os.path.join(datadir_path, 'addrs')):
158             try:
159                 with open(os.path.join(datadir_path, 'addrs'), 'rb') as f:
160                     addrs.update(dict((tuple(k), v) for k, v in json.loads(f.read())))
161             except:
162                 print >>sys.stderr, 'error parsing addrs'
163         for addr_df in map(parse, net.BOOTSTRAP_ADDRS):
164             try:
165                 addr = yield addr_df
166                 if addr not in addrs:
167                     addrs[addr] = (0, time.time(), time.time())
168             except:
169                 log.err()
170         
171         connect_addrs = set()
172         for addr_df in map(parse, args.p2pool_nodes):
173             try:
174                 connect_addrs.add((yield addr_df))
175             except:
176                 log.err()
177         
178         node.p2p_node = p2pool_node.P2PNode(node,
179             port=args.p2pool_port,
180             max_incoming_conns=args.p2pool_conns,
181             addr_store=addrs,
182             connect_addrs=connect_addrs,
183             desired_outgoing_conns=args.p2pool_outgoing_conns,
184         )
185         node.p2p_node.start()
186         
187         def save_addrs():
188             with open(os.path.join(datadir_path, 'addrs'), 'wb') as f:
189                 f.write(json.dumps(node.p2p_node.addr_store.items()))
190         task.LoopingCall(save_addrs).start(60)
191         
192         print '    ...success!'
193         print
194         
195         if args.upnp:
196             @defer.inlineCallbacks
197             def upnp_thread():
198                 while True:
199                     try:
200                         is_lan, lan_ip = yield ipdiscover.get_local_ip()
201                         if is_lan:
202                             pm = yield portmapper.get_port_mapper()
203                             yield pm._upnp.add_port_mapping(lan_ip, args.p2pool_port, args.p2pool_port, 'p2pool', 'TCP')
204                     except defer.TimeoutError:
205                         pass
206                     except:
207                         if p2pool.DEBUG:
208                             log.err(None, 'UPnP error:')
209                     yield deferral.sleep(random.expovariate(1/120))
210             upnp_thread()
211         
212         # start listening for workers with a JSON-RPC server
213         
214         print 'Listening for workers on %r port %i...' % (worker_endpoint[0], worker_endpoint[1])
215         
216         wb = work.WorkerBridge(node, my_pubkey, args.donation_percentage, merged_urls, args.worker_fee)
217         web_root = web.get_web_root(wb, datadir_path, bitcoind_warning_var)
218         caching_wb = worker_interface.CachingWorkerBridge(wb)
219         worker_interface.WorkerInterface(caching_wb).attach_to(web_root, get_handler=lambda request: request.redirect('/static/'))
220         web_serverfactory = server.Site(web_root)
221         
222         
223         serverfactory = switchprotocol.FirstByteSwitchFactory({'{': stratum.StratumServerFactory(caching_wb)}, web_serverfactory)
224         deferral.retry('Error binding to worker port:', traceback=False)(reactor.listenTCP)(worker_endpoint[1], serverfactory, interface=worker_endpoint[0])
225         
226         with open(os.path.join(os.path.join(datadir_path, 'ready_flag')), 'wb') as f:
227             pass
228         
229         print '    ...success!'
230         print
231         
232         
233         # done!
234         print 'Started successfully!'
235         print 'Go to http://127.0.0.1:%i/ to view graphs and statistics!' % (worker_endpoint[1],)
236         if args.donation_percentage > 1.1:
237             print '''Donating %.1f%% of work towards P2Pool's development. Thanks for the tip!''' % (args.donation_percentage,)
238         elif args.donation_percentage < .9:
239             print '''Donating %.1f%% of work towards P2Pool's development. Please donate to encourage further development of P2Pool!''' % (args.donation_percentage,)
240         else:
241             print '''Donating %.1f%% of work towards P2Pool's development. Thank you!''' % (args.donation_percentage,)
242             print 'You can increase this amount with --give-author argument! (or decrease it, if you must)'
243         print
244         
245         
246         if hasattr(signal, 'SIGALRM'):
247             signal.signal(signal.SIGALRM, lambda signum, frame: reactor.callFromThread(
248                 sys.stderr.write, 'Watchdog timer went off at:\n' + ''.join(traceback.format_stack())
249             ))
250             signal.siginterrupt(signal.SIGALRM, False)
251             task.LoopingCall(signal.alarm, 30).start(1)
252         
253         if args.irc_announce:
254             from twisted.words.protocols import irc
255             class IRCClient(irc.IRCClient):
256                 nickname = 'p2pool%02i' % (random.randrange(100),)
257                 channel = net.ANNOUNCE_CHANNEL
258                 def lineReceived(self, line):
259                     if p2pool.DEBUG:
260                         print repr(line)
261                     irc.IRCClient.lineReceived(self, line)
262                 def signedOn(self):
263                     self.in_channel = False
264                     irc.IRCClient.signedOn(self)
265                     self.factory.resetDelay()
266                     self.join(self.channel)
267                     @defer.inlineCallbacks
268                     def new_share(share):
269                         if not self.in_channel:
270                             return
271                         if share.pow_hash <= share.header['bits'].target and abs(share.timestamp - time.time()) < 10*60:
272                             yield deferral.sleep(random.expovariate(1/60))
273                             message = '\x02%s BLOCK FOUND by %s! %s%064x' % (net.NAME.upper(), bitcoin_data.script2_to_address(share.new_script, net.PARENT), net.PARENT.BLOCK_EXPLORER_URL_PREFIX, share.header_hash)
274                             if all('%x' % (share.header_hash,) not in old_message for old_message in self.recent_messages):
275                                 self.say(self.channel, message)
276                                 self._remember_message(message)
277                     self.watch_id = node.tracker.verified.added.watch(new_share)
278                     self.recent_messages = []
279                 def joined(self, channel):
280                     self.in_channel = True
281                 def left(self, channel):
282                     self.in_channel = False
283                 def _remember_message(self, message):
284                     self.recent_messages.append(message)
285                     while len(self.recent_messages) > 100:
286                         self.recent_messages.pop(0)
287                 def privmsg(self, user, channel, message):
288                     if channel == self.channel:
289                         self._remember_message(message)
290                 def connectionLost(self, reason):
291                     node.tracker.verified.added.unwatch(self.watch_id)
292                     print 'IRC connection lost:', reason.getErrorMessage()
293             class IRCClientFactory(protocol.ReconnectingClientFactory):
294                 protocol = IRCClient
295             reactor.connectTCP("irc.freenode.net", 6667, IRCClientFactory())
296         
297         @defer.inlineCallbacks
298         def status_thread():
299             last_str = None
300             last_time = 0
301             while True:
302                 yield deferral.sleep(3)
303                 try:
304                     height = node.tracker.get_height(node.best_share_var.value)
305                     this_str = 'P2Pool: %i shares in chain (%i verified/%i total) Peers: %i (%i incoming)' % (
306                         height,
307                         len(node.tracker.verified.items),
308                         len(node.tracker.items),
309                         len(node.p2p_node.peers),
310                         sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming),
311                     ) + (' FDs: %i R/%i W' % (len(reactor.getReaders()), len(reactor.getWriters())) if p2pool.DEBUG else '')
312                     
313                     datums, dt = wb.local_rate_monitor.get_datums_in_last()
314                     my_att_s = sum(datum['work']/dt for datum in datums)
315                     this_str += '\n Local: %sH/s in last %s Local dead on arrival: %s Expected time to share: %s' % (
316                         math.format(int(my_att_s)),
317                         math.format_dt(dt),
318                         math.format_binomial_conf(sum(1 for datum in datums if datum['dead']), len(datums), 0.95),
319                         math.format_dt(2**256 / node.tracker.items[node.best_share_var.value].max_target / my_att_s) if my_att_s and node.best_share_var.value else '???',
320                     )
321                     
322                     if height > 2:
323                         (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts()
324                         stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, min(60*60//net.SHARE_PERIOD, height))
325                         real_att_s = p2pool_data.get_pool_attempts_per_second(node.tracker, node.best_share_var.value, min(height - 1, 60*60//net.SHARE_PERIOD)) / (1 - stale_prop)
326                         
327                         this_str += '\n Shares: %i (%i orphan, %i dead) Stale rate: %s Efficiency: %s Current payout: %.4f %s' % (
328                             shares, stale_orphan_shares, stale_doa_shares,
329                             math.format_binomial_conf(stale_orphan_shares + stale_doa_shares, shares, 0.95),
330                             math.format_binomial_conf(stale_orphan_shares + stale_doa_shares, shares, 0.95, lambda x: (1 - x)/(1 - stale_prop)),
331                             node.get_current_txouts().get(bitcoin_data.pubkey_to_script2(my_pubkey), 0) * 1e-6, net.PARENT.SYMBOL,
332                         )
333                         this_str += '\n Pool: %sH/s Stale rate: %.1f%% Expected time to block: %s' % (
334                             math.format(int(real_att_s)),
335                             100*stale_prop,
336                             math.format_dt(2**256 / node.bitcoind_work.value['bits'].target / real_att_s),
337                         )
338                         
339                         for warning in p2pool_data.get_warnings(node.tracker, node.best_share_var.value, net, bitcoind_warning_var.value, node.bitcoind_work.value):
340                             print >>sys.stderr, '#'*40
341                             print >>sys.stderr, '>>> Warning: ' + warning
342                             print >>sys.stderr, '#'*40
343                         
344                         if gc.garbage:
345                             print '%i pieces of uncollectable cyclic garbage! Types: %r' % (len(gc.garbage), map(type, gc.garbage))
346                     
347                     if this_str != last_str or time.time() > last_time + 15:
348                         print this_str
349                         last_str = this_str
350                         last_time = time.time()
351                 except:
352                     log.err()
353         status_thread()
354     except:
355         reactor.stop()
356         log.err(None, 'Fatal error:')
357
358 def run():
359     realnets = dict((name, net) for name, net in networks.nets.iteritems() if '_testnet' not in name)
360     
361     parser = fixargparse.FixedArgumentParser(description='p2pool (version %s)' % (p2pool.__version__,), fromfile_prefix_chars='@')
362     parser.add_argument('--version', action='version', version=p2pool.__version__)
363     parser.add_argument('--net',
364         help='use specified network (default: bitcoin)',
365         action='store', choices=sorted(realnets), default='bitcoin', dest='net_name')
366     parser.add_argument('--testnet',
367         help='''use the network's testnet''',
368         action='store_const', const=True, default=False, dest='testnet')
369     parser.add_argument('--debug',
370         help='enable debugging mode',
371         action='store_const', const=True, default=False, dest='debug')
372     parser.add_argument('-a', '--address',
373         help='generate payouts to this address (default: <address requested from bitcoind>)',
374         type=str, action='store', default=None, dest='address')
375     parser.add_argument('--datadir',
376         help='store data in this directory (default: <directory run_p2pool.py is in>/data)',
377         type=str, action='store', default=None, dest='datadir')
378     parser.add_argument('--logfile',
379         help='''log to this file (default: data/<NET>/log)''',
380         type=str, action='store', default=None, dest='logfile')
381     parser.add_argument('--merged',
382         help='call getauxblock on this url to get work for merged mining (example: http://ncuser:ncpass@127.0.0.1:10332/)',
383         type=str, action='append', default=[], dest='merged_urls')
384     parser.add_argument('--give-author', metavar='DONATION_PERCENTAGE',
385         help='donate this percentage of work towards the development of p2pool (default: 1.0)',
386         type=float, action='store', default=1.0, dest='donation_percentage')
387     parser.add_argument('--iocp',
388         help='use Windows IOCP API in order to avoid errors due to large number of sockets being open',
389         action='store_true', default=False, dest='iocp')
390     parser.add_argument('--irc-announce',
391         help='announce any blocks found on irc://irc.freenode.net/#p2pool',
392         action='store_true', default=False, dest='irc_announce')
393     parser.add_argument('--no-bugreport',
394         help='disable submitting caught exceptions to the author',
395         action='store_true', default=False, dest='no_bugreport')
396     
397     p2pool_group = parser.add_argument_group('p2pool interface')
398     p2pool_group.add_argument('--p2pool-port', metavar='PORT',
399         help='use port PORT to listen for connections (forward this port from your router!) (default: %s)' % ', '.join('%s:%i' % (name, net.P2P_PORT) for name, net in sorted(realnets.items())),
400         type=int, action='store', default=None, dest='p2pool_port')
401     p2pool_group.add_argument('-n', '--p2pool-node', metavar='ADDR[:PORT]',
402         help='connect to existing p2pool node at ADDR listening on port PORT (defaults to default p2pool P2P port) in addition to builtin addresses',
403         type=str, action='append', default=[], dest='p2pool_nodes')
404     parser.add_argument('--disable-upnp',
405         help='''don't attempt to use UPnP to forward p2pool's P2P port from the Internet to this computer''',
406         action='store_false', default=True, dest='upnp')
407     p2pool_group.add_argument('--max-conns', metavar='CONNS',
408         help='maximum incoming connections (default: 40)',
409         type=int, action='store', default=40, dest='p2pool_conns')
410     p2pool_group.add_argument('--outgoing-conns', metavar='CONNS',
411         help='outgoing connections (default: 6)',
412         type=int, action='store', default=6, dest='p2pool_outgoing_conns')
413     
414     worker_group = parser.add_argument_group('worker interface')
415     worker_group.add_argument('-w', '--worker-port', metavar='PORT or ADDR:PORT',
416         help='listen on PORT on interface with ADDR for RPC connections from miners (default: all interfaces, %s)' % ', '.join('%s:%i' % (name, net.WORKER_PORT) for name, net in sorted(realnets.items())),
417         type=str, action='store', default=None, dest='worker_endpoint')
418     worker_group.add_argument('-f', '--fee', metavar='FEE_PERCENTAGE',
419         help='''charge workers mining to their own bitcoin address (by setting their miner's username to a bitcoin address) this percentage fee to mine on your p2pool instance. Amount displayed at http://127.0.0.1:WORKER_PORT/fee (default: 0)''',
420         type=float, action='store', default=0, dest='worker_fee')
421     
422     bitcoind_group = parser.add_argument_group('bitcoind interface')
423     bitcoind_group.add_argument('--bitcoind-address', metavar='BITCOIND_ADDRESS',
424         help='connect to this address (default: 127.0.0.1)',
425         type=str, action='store', default='127.0.0.1', dest='bitcoind_address')
426     bitcoind_group.add_argument('--bitcoind-rpc-port', metavar='BITCOIND_RPC_PORT',
427         help='''connect to JSON-RPC interface at this port (default: %s <read from bitcoin.conf if password not provided>)''' % ', '.join('%s:%i' % (name, net.PARENT.RPC_PORT) for name, net in sorted(realnets.items())),
428         type=int, action='store', default=None, dest='bitcoind_rpc_port')
429     bitcoind_group.add_argument('--bitcoind-rpc-ssl',
430         help='connect to JSON-RPC interface using SSL',
431         action='store_true', default=False, dest='bitcoind_rpc_ssl')
432     bitcoind_group.add_argument('--bitcoind-p2p-port', metavar='BITCOIND_P2P_PORT',
433         help='''connect to P2P interface at this port (default: %s <read from bitcoin.conf if password not provided>)''' % ', '.join('%s:%i' % (name, net.PARENT.P2P_PORT) for name, net in sorted(realnets.items())),
434         type=int, action='store', default=None, dest='bitcoind_p2p_port')
435     
436     bitcoind_group.add_argument(metavar='BITCOIND_RPCUSERPASS',
437         help='bitcoind RPC interface username, then password, space-separated (only one being provided will cause the username to default to being empty, and none will cause P2Pool to read them from bitcoin.conf)',
438         type=str, action='store', default=[], nargs='*', dest='bitcoind_rpc_userpass')
439     
440     args = parser.parse_args()
441     
442     if args.debug:
443         p2pool.DEBUG = True
444         defer.setDebugging(True)
445     else:
446         p2pool.DEBUG = False
447     
448     net_name = args.net_name + ('_testnet' if args.testnet else '')
449     net = networks.nets[net_name]
450     
451     datadir_path = os.path.join((os.path.join(os.path.dirname(sys.argv[0]), 'data') if args.datadir is None else args.datadir), net_name)
452     if not os.path.exists(datadir_path):
453         os.makedirs(datadir_path)
454     
455     if len(args.bitcoind_rpc_userpass) > 2:
456         parser.error('a maximum of two arguments are allowed')
457     args.bitcoind_rpc_username, args.bitcoind_rpc_password = ([None, None] + args.bitcoind_rpc_userpass)[-2:]
458     
459     if args.bitcoind_rpc_password is None:
460         conf_path = net.PARENT.CONF_FILE_FUNC()
461         if not os.path.exists(conf_path):
462             parser.error('''Bitcoin configuration file not found. Manually enter your RPC password.\r\n'''
463                 '''If you actually haven't created a configuration file, you should create one at %s with the text:\r\n'''
464                 '''\r\n'''
465                 '''server=1\r\n'''
466                 '''rpcpassword=%x\r\n'''
467                 '''\r\n'''
468                 '''Keep that password secret! After creating the file, restart Bitcoin.''' % (conf_path, random.randrange(2**128)))
469         conf = open(conf_path, 'rb').read()
470         contents = {}
471         for line in conf.splitlines(True):
472             if '#' in line:
473                 line = line[:line.index('#')]
474             if '=' not in line:
475                 continue
476             k, v = line.split('=', 1)
477             contents[k.strip()] = v.strip()
478         for conf_name, var_name, var_type in [
479             ('rpcuser', 'bitcoind_rpc_username', str),
480             ('rpcpassword', 'bitcoind_rpc_password', str),
481             ('rpcport', 'bitcoind_rpc_port', int),
482             ('port', 'bitcoind_p2p_port', int),
483         ]:
484             if getattr(args, var_name) is None and conf_name in contents:
485                 setattr(args, var_name, var_type(contents[conf_name]))
486         if args.bitcoind_rpc_password is None:
487             parser.error('''Bitcoin configuration file didn't contain an rpcpassword= line! Add one!''')
488     
489     if args.bitcoind_rpc_username is None:
490         args.bitcoind_rpc_username = ''
491     
492     if args.bitcoind_rpc_port is None:
493         args.bitcoind_rpc_port = net.PARENT.RPC_PORT
494     
495     if args.bitcoind_p2p_port is None:
496         args.bitcoind_p2p_port = net.PARENT.P2P_PORT
497     
498     if args.p2pool_port is None:
499         args.p2pool_port = net.P2P_PORT
500     
501     if args.p2pool_outgoing_conns > 10:
502         parser.error('''--outgoing-conns can't be more than 10''')
503     
504     if args.worker_endpoint is None:
505         worker_endpoint = '', net.WORKER_PORT
506     elif ':' not in args.worker_endpoint:
507         worker_endpoint = '', int(args.worker_endpoint)
508     else:
509         addr, port = args.worker_endpoint.rsplit(':', 1)
510         worker_endpoint = addr, int(port)
511
512     def separate_url(url):
513         s = urlparse.urlsplit(url)
514         if '@' not in s.netloc:
515             parser.error('merged url netloc must contain an "@"')
516         userpass, new_netloc = s.netloc.rsplit('@', 1)
517         return urlparse.urlunsplit(s._replace(netloc=new_netloc)), userpass
518     merged_urls = map(separate_url, args.merged_urls)
519     
520     if args.logfile is None:
521         args.logfile = os.path.join(datadir_path, 'log')
522     
523     logfile = logging.LogFile(args.logfile)
524     pipe = logging.TimestampingPipe(logging.TeePipe([logging.EncodeReplacerPipe(sys.stderr), logfile]))
525     sys.stdout = logging.AbortPipe(pipe)
526     sys.stderr = log.DefaultObserver.stderr = logging.AbortPipe(logging.PrefixPipe(pipe, '> '))
527     if hasattr(signal, "SIGUSR1"):
528         def sigusr1(signum, frame):
529             print 'Caught SIGUSR1, closing %r...' % (args.logfile,)
530             logfile.reopen()
531             print '...and reopened %r after catching SIGUSR1.' % (args.logfile,)
532         signal.signal(signal.SIGUSR1, sigusr1)
533     task.LoopingCall(logfile.reopen).start(5)
534     
535     class ErrorReporter(object):
536         def __init__(self):
537             self.last_sent = None
538         
539         def emit(self, eventDict):
540             if not eventDict["isError"]:
541                 return
542             
543             if self.last_sent is not None and time.time() < self.last_sent + 5:
544                 return
545             self.last_sent = time.time()
546             
547             if 'failure' in eventDict:
548                 text = ((eventDict.get('why') or 'Unhandled Error')
549                     + '\n' + eventDict['failure'].getTraceback())
550             else:
551                 text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
552             
553             from twisted.web import client
554             client.getPage(
555                 url='http://u.forre.st/p2pool_error.cgi',
556                 method='POST',
557                 postdata=p2pool.__version__ + ' ' + net.NAME + '\n' + text,
558                 timeout=15,
559             ).addBoth(lambda x: None)
560     if not args.no_bugreport:
561         log.addObserver(ErrorReporter().emit)
562     
563     reactor.callWhenRunning(main, args, net, datadir_path, merged_urls, worker_endpoint)
564     reactor.run()