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