warn user about bitcoin connection slots if p2p connect takes longer than 5 seconds
[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, tcp
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             def long():
41                 print '''    ...taking a while. Common reasons for this include all of bitcoind's connection slots being used...'''
42             long_dc = reactor.callLater(5, long)
43             yield factory.getProtocol() # waits until handshake is successful
44             if not long_dc.called: long_dc.cancel()
45             print '    ...success!'
46             print
47             defer.returnValue(factory)
48         
49         if args.testnet: # establish p2p connection first if testnet so bitcoind can work without connections
50             factory = yield connect_p2p()
51         
52         # connect to bitcoind over JSON-RPC and do initial getmemorypool
53         url = '%s://%s:%i/' % ('https' if args.bitcoind_rpc_ssl else 'http', args.bitcoind_address, args.bitcoind_rpc_port)
54         print '''Testing bitcoind RPC connection to '%s' with username '%s'...''' % (url, args.bitcoind_rpc_username)
55         bitcoind = jsonrpc.HTTPProxy(url, dict(Authorization='Basic ' + base64.b64encode(args.bitcoind_rpc_username + ':' + args.bitcoind_rpc_password)), timeout=30)
56         yield helper.check(bitcoind, net)
57         temp_work = yield helper.getwork(bitcoind)
58         
59         bitcoind_warning_var = variable.Variable(None)
60         @defer.inlineCallbacks
61         def poll_warnings():
62             errors = (yield deferral.retry('Error while calling getmininginfo:')(bitcoind.rpc_getmininginfo)())['errors']
63             bitcoind_warning_var.set(errors if errors != '' else None)
64         yield poll_warnings()
65         deferral.RobustLoopingCall(poll_warnings).start(20*60)
66         
67         print '    ...success!'
68         print '    Current block hash: %x' % (temp_work['previous_block'],)
69         print '    Current block height: %i' % (temp_work['height'] - 1,)
70         print
71         
72         if not args.testnet:
73             factory = yield connect_p2p()
74         
75         print 'Determining payout address...'
76         if args.pubkey_hash is None:
77             address_path = os.path.join(datadir_path, 'cached_payout_address')
78             
79             if os.path.exists(address_path):
80                 with open(address_path, 'rb') as f:
81                     address = f.read().strip('\r\n')
82                 print '    Loaded cached address: %s...' % (address,)
83             else:
84                 address = None
85             
86             if address is not None:
87                 res = yield deferral.retry('Error validating cached address:', 5)(lambda: bitcoind.rpc_validateaddress(address))()
88                 if not res['isvalid'] or not res['ismine']:
89                     print '    Cached address is either invalid or not controlled by local bitcoind!'
90                     address = None
91             
92             if address is None:
93                 print '    Getting payout address from bitcoind...'
94                 address = yield deferral.retry('Error getting payout address from bitcoind:', 5)(lambda: bitcoind.rpc_getaccountaddress('p2pool'))()
95             
96             with open(address_path, 'wb') as f:
97                 f.write(address)
98             
99             my_pubkey_hash = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
100         else:
101             my_pubkey_hash = args.pubkey_hash
102         print '    ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT)
103         print
104         
105         print "Loading shares..."
106         shares = {}
107         known_verified = set()
108         def share_cb(share):
109             share.time_seen = 0 # XXX
110             shares[share.hash] = share
111             if len(shares) % 1000 == 0 and shares:
112                 print "    %i" % (len(shares),)
113         ss = p2pool_data.ShareStore(os.path.join(datadir_path, 'shares.'), net, share_cb, known_verified.add)
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         node.tracker.removed.watch(lambda share: ss.forget_share(share.hash))
130         node.tracker.verified.removed.watch(lambda share: ss.forget_verified_share(share.hash))
131         
132         def save_shares():
133             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)):
134                 ss.add_share(share)
135                 if share.hash in node.tracker.verified.items:
136                     ss.add_verified_hash(share.hash)
137         deferral.RobustLoopingCall(save_shares).start(60)
138         
139         print '    ...success!'
140         print
141         
142         
143         print 'Joining p2pool network using port %i...' % (args.p2pool_port,)
144         
145         @defer.inlineCallbacks
146         def parse(host):
147             port = net.P2P_PORT
148             if ':' in host:
149                 host, port_str = host.split(':')
150                 port = int(port_str)
151             defer.returnValue(((yield reactor.resolve(host)), port))
152         
153         addrs = {}
154         if os.path.exists(os.path.join(datadir_path, 'addrs')):
155             try:
156                 with open(os.path.join(datadir_path, 'addrs'), 'rb') as f:
157                     addrs.update(dict((tuple(k), v) for k, v in json.loads(f.read())))
158             except:
159                 print >>sys.stderr, 'error parsing addrs'
160         for addr_df in map(parse, net.BOOTSTRAP_ADDRS):
161             try:
162                 addr = yield addr_df
163                 if addr not in addrs:
164                     addrs[addr] = (0, time.time(), time.time())
165             except:
166                 log.err()
167         
168         connect_addrs = set()
169         for addr_df in map(parse, args.p2pool_nodes):
170             try:
171                 connect_addrs.add((yield addr_df))
172             except:
173                 log.err()
174         
175         node.p2p_node = p2pool_node.P2PNode(node,
176             port=args.p2pool_port,
177             max_incoming_conns=args.p2pool_conns,
178             addr_store=addrs,
179             connect_addrs=connect_addrs,
180             desired_outgoing_conns=args.p2pool_outgoing_conns,
181         )
182         node.p2p_node.start()
183         
184         def save_addrs():
185             with open(os.path.join(datadir_path, 'addrs'), 'wb') as f:
186                 f.write(json.dumps(node.p2p_node.addr_store.items()))
187         deferral.RobustLoopingCall(save_addrs).start(60)
188         
189         print '    ...success!'
190         print
191         
192         if args.upnp:
193             @defer.inlineCallbacks
194             def upnp_thread():
195                 while True:
196                     try:
197                         is_lan, lan_ip = yield ipdiscover.get_local_ip()
198                         if is_lan:
199                             pm = yield portmapper.get_port_mapper()
200                             yield pm._upnp.add_port_mapping(lan_ip, args.p2pool_port, args.p2pool_port, 'p2pool', 'TCP')
201                     except defer.TimeoutError:
202                         pass
203                     except:
204                         if p2pool.DEBUG:
205                             log.err(None, 'UPnP error:')
206                     yield deferral.sleep(random.expovariate(1/120))
207             upnp_thread()
208         
209         # start listening for workers with a JSON-RPC server
210         
211         print 'Listening for workers on %r port %i...' % (worker_endpoint[0], worker_endpoint[1])
212         
213         wb = work.WorkerBridge(node, my_pubkey_hash, args.donation_percentage, merged_urls, args.worker_fee)
214         web_root = web.get_web_root(wb, datadir_path, bitcoind_warning_var)
215         caching_wb = worker_interface.CachingWorkerBridge(wb)
216         worker_interface.WorkerInterface(caching_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(caching_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             deferral.RobustLoopingCall(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                     my_shares_per_s = sum(datum['work']/dt/bitcoin_data.target_to_average_attempts(datum['share_target']) for datum in datums)
313                     this_str += '\n Local: %sH/s in last %s Local dead on arrival: %s Expected time to share: %s' % (
314                         math.format(int(my_att_s)),
315                         math.format_dt(dt),
316                         math.format_binomial_conf(sum(1 for datum in datums if datum['dead']), len(datums), 0.95),
317                         math.format_dt(1/my_shares_per_s) if my_shares_per_s else '???',
318                     )
319                     
320                     if height > 2:
321                         (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts()
322                         stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, min(60*60//net.SHARE_PERIOD, height))
323                         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)
324                         
325                         this_str += '\n Shares: %i (%i orphan, %i dead) Stale rate: %s Efficiency: %s Current payout: %.4f %s' % (
326                             shares, stale_orphan_shares, stale_doa_shares,
327                             math.format_binomial_conf(stale_orphan_shares + stale_doa_shares, shares, 0.95),
328                             math.format_binomial_conf(stale_orphan_shares + stale_doa_shares, shares, 0.95, lambda x: (1 - x)/(1 - stale_prop)),
329                             node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(my_pubkey_hash), 0)*1e-8, net.PARENT.SYMBOL,
330                         )
331                         this_str += '\n Pool: %sH/s Stale rate: %.1f%% Expected time to block: %s' % (
332                             math.format(int(real_att_s)),
333                             100*stale_prop,
334                             math.format_dt(2**256 / node.bitcoind_work.value['bits'].target / real_att_s),
335                         )
336                         
337                         for warning in p2pool_data.get_warnings(node.tracker, node.best_share_var.value, net, bitcoind_warning_var.value, node.bitcoind_work.value):
338                             print >>sys.stderr, '#'*40
339                             print >>sys.stderr, '>>> Warning: ' + warning
340                             print >>sys.stderr, '#'*40
341                         
342                         if gc.garbage:
343                             print '%i pieces of uncollectable cyclic garbage! Types: %r' % (len(gc.garbage), map(type, gc.garbage))
344                     
345                     if this_str != last_str or time.time() > last_time + 15:
346                         print this_str
347                         last_str = this_str
348                         last_time = time.time()
349                 except:
350                     log.err()
351         status_thread()
352     except:
353         reactor.stop()
354         log.err(None, 'Fatal error:')
355
356 def run():
357     if not hasattr(tcp.Client, 'abortConnection'):
358         print "Twisted doesn't have abortConnection! Upgrade to a newer version of Twisted to avoid memory leaks!"
359         print 'Pausing for 3 seconds...'
360         time.sleep(3)
361     
362     realnets = dict((name, net) for name, net in networks.nets.iteritems() if '_testnet' not in name)
363     
364     parser = fixargparse.FixedArgumentParser(description='p2pool (version %s)' % (p2pool.__version__,), fromfile_prefix_chars='@')
365     parser.add_argument('--version', action='version', version=p2pool.__version__)
366     parser.add_argument('--net',
367         help='use specified network (default: bitcoin)',
368         action='store', choices=sorted(realnets), default='bitcoin', dest='net_name')
369     parser.add_argument('--testnet',
370         help='''use the network's testnet''',
371         action='store_const', const=True, default=False, dest='testnet')
372     parser.add_argument('--debug',
373         help='enable debugging mode',
374         action='store_const', const=True, default=False, dest='debug')
375     parser.add_argument('-a', '--address',
376         help='generate payouts to this address (default: <address requested from bitcoind>)',
377         type=str, action='store', default=None, dest='address')
378     parser.add_argument('--datadir',
379         help='store data in this directory (default: <directory run_p2pool.py is in>/data)',
380         type=str, action='store', default=None, dest='datadir')
381     parser.add_argument('--logfile',
382         help='''log to this file (default: data/<NET>/log)''',
383         type=str, action='store', default=None, dest='logfile')
384     parser.add_argument('--merged',
385         help='call getauxblock on this url to get work for merged mining (example: http://ncuser:ncpass@127.0.0.1:10332/)',
386         type=str, action='append', default=[], dest='merged_urls')
387     parser.add_argument('--give-author', metavar='DONATION_PERCENTAGE',
388         help='donate this percentage of work towards the development of p2pool (default: 1.0)',
389         type=float, action='store', default=1.0, dest='donation_percentage')
390     parser.add_argument('--iocp',
391         help='use Windows IOCP API in order to avoid errors due to large number of sockets being open',
392         action='store_true', default=False, dest='iocp')
393     parser.add_argument('--irc-announce',
394         help='announce any blocks found on irc://irc.freenode.net/#p2pool',
395         action='store_true', default=False, dest='irc_announce')
396     parser.add_argument('--no-bugreport',
397         help='disable submitting caught exceptions to the author',
398         action='store_true', default=False, dest='no_bugreport')
399     
400     p2pool_group = parser.add_argument_group('p2pool interface')
401     p2pool_group.add_argument('--p2pool-port', metavar='PORT',
402         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())),
403         type=int, action='store', default=None, dest='p2pool_port')
404     p2pool_group.add_argument('-n', '--p2pool-node', metavar='ADDR[:PORT]',
405         help='connect to existing p2pool node at ADDR listening on port PORT (defaults to default p2pool P2P port) in addition to builtin addresses',
406         type=str, action='append', default=[], dest='p2pool_nodes')
407     parser.add_argument('--disable-upnp',
408         help='''don't attempt to use UPnP to forward p2pool's P2P port from the Internet to this computer''',
409         action='store_false', default=True, dest='upnp')
410     p2pool_group.add_argument('--max-conns', metavar='CONNS',
411         help='maximum incoming connections (default: 40)',
412         type=int, action='store', default=40, dest='p2pool_conns')
413     p2pool_group.add_argument('--outgoing-conns', metavar='CONNS',
414         help='outgoing connections (default: 6)',
415         type=int, action='store', default=6, dest='p2pool_outgoing_conns')
416     
417     worker_group = parser.add_argument_group('worker interface')
418     worker_group.add_argument('-w', '--worker-port', metavar='PORT or ADDR:PORT',
419         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())),
420         type=str, action='store', default=None, dest='worker_endpoint')
421     worker_group.add_argument('-f', '--fee', metavar='FEE_PERCENTAGE',
422         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)''',
423         type=float, action='store', default=0, dest='worker_fee')
424     
425     bitcoind_group = parser.add_argument_group('bitcoind interface')
426     bitcoind_group.add_argument('--bitcoind-address', metavar='BITCOIND_ADDRESS',
427         help='connect to this address (default: 127.0.0.1)',
428         type=str, action='store', default='127.0.0.1', dest='bitcoind_address')
429     bitcoind_group.add_argument('--bitcoind-rpc-port', metavar='BITCOIND_RPC_PORT',
430         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())),
431         type=int, action='store', default=None, dest='bitcoind_rpc_port')
432     bitcoind_group.add_argument('--bitcoind-rpc-ssl',
433         help='connect to JSON-RPC interface using SSL',
434         action='store_true', default=False, dest='bitcoind_rpc_ssl')
435     bitcoind_group.add_argument('--bitcoind-p2p-port', metavar='BITCOIND_P2P_PORT',
436         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())),
437         type=int, action='store', default=None, dest='bitcoind_p2p_port')
438     
439     bitcoind_group.add_argument(metavar='BITCOIND_RPCUSERPASS',
440         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)',
441         type=str, action='store', default=[], nargs='*', dest='bitcoind_rpc_userpass')
442     
443     args = parser.parse_args()
444     
445     if args.debug:
446         p2pool.DEBUG = True
447         defer.setDebugging(True)
448     else:
449         p2pool.DEBUG = False
450     
451     net_name = args.net_name + ('_testnet' if args.testnet else '')
452     net = networks.nets[net_name]
453     
454     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)
455     if not os.path.exists(datadir_path):
456         os.makedirs(datadir_path)
457     
458     if len(args.bitcoind_rpc_userpass) > 2:
459         parser.error('a maximum of two arguments are allowed')
460     args.bitcoind_rpc_username, args.bitcoind_rpc_password = ([None, None] + args.bitcoind_rpc_userpass)[-2:]
461     
462     if args.bitcoind_rpc_password is None:
463         conf_path = net.PARENT.CONF_FILE_FUNC()
464         if not os.path.exists(conf_path):
465             parser.error('''Bitcoin configuration file not found. Manually enter your RPC password.\r\n'''
466                 '''If you actually haven't created a configuration file, you should create one at %s with the text:\r\n'''
467                 '''\r\n'''
468                 '''server=1\r\n'''
469                 '''rpcpassword=%x\r\n'''
470                 '''\r\n'''
471                 '''Keep that password secret! After creating the file, restart Bitcoin.''' % (conf_path, random.randrange(2**128)))
472         conf = open(conf_path, 'rb').read()
473         contents = {}
474         for line in conf.splitlines(True):
475             if '#' in line:
476                 line = line[:line.index('#')]
477             if '=' not in line:
478                 continue
479             k, v = line.split('=', 1)
480             contents[k.strip()] = v.strip()
481         for conf_name, var_name, var_type in [
482             ('rpcuser', 'bitcoind_rpc_username', str),
483             ('rpcpassword', 'bitcoind_rpc_password', str),
484             ('rpcport', 'bitcoind_rpc_port', int),
485             ('port', 'bitcoind_p2p_port', int),
486         ]:
487             if getattr(args, var_name) is None and conf_name in contents:
488                 setattr(args, var_name, var_type(contents[conf_name]))
489         if args.bitcoind_rpc_password is None:
490             parser.error('''Bitcoin configuration file didn't contain an rpcpassword= line! Add one!''')
491     
492     if args.bitcoind_rpc_username is None:
493         args.bitcoind_rpc_username = ''
494     
495     if args.bitcoind_rpc_port is None:
496         args.bitcoind_rpc_port = net.PARENT.RPC_PORT
497     
498     if args.bitcoind_p2p_port is None:
499         args.bitcoind_p2p_port = net.PARENT.P2P_PORT
500     
501     if args.p2pool_port is None:
502         args.p2pool_port = net.P2P_PORT
503     
504     if args.p2pool_outgoing_conns > 10:
505         parser.error('''--outgoing-conns can't be more than 10''')
506     
507     if args.worker_endpoint is None:
508         worker_endpoint = '', net.WORKER_PORT
509     elif ':' not in args.worker_endpoint:
510         worker_endpoint = '', int(args.worker_endpoint)
511     else:
512         addr, port = args.worker_endpoint.rsplit(':', 1)
513         worker_endpoint = addr, int(port)
514     
515     if args.address is not None:
516         try:
517             args.pubkey_hash = bitcoin_data.address_to_pubkey_hash(args.address, net.PARENT)
518         except Exception, e:
519             parser.error('error parsing address: ' + repr(e))
520     else:
521         args.pubkey_hash = None
522     
523     def separate_url(url):
524         s = urlparse.urlsplit(url)
525         if '@' not in s.netloc:
526             parser.error('merged url netloc must contain an "@"')
527         userpass, new_netloc = s.netloc.rsplit('@', 1)
528         return urlparse.urlunsplit(s._replace(netloc=new_netloc)), userpass
529     merged_urls = map(separate_url, args.merged_urls)
530     
531     if args.logfile is None:
532         args.logfile = os.path.join(datadir_path, 'log')
533     
534     logfile = logging.LogFile(args.logfile)
535     pipe = logging.TimestampingPipe(logging.TeePipe([logging.EncodeReplacerPipe(sys.stderr), logfile]))
536     sys.stdout = logging.AbortPipe(pipe)
537     sys.stderr = log.DefaultObserver.stderr = logging.AbortPipe(logging.PrefixPipe(pipe, '> '))
538     if hasattr(signal, "SIGUSR1"):
539         def sigusr1(signum, frame):
540             print 'Caught SIGUSR1, closing %r...' % (args.logfile,)
541             logfile.reopen()
542             print '...and reopened %r after catching SIGUSR1.' % (args.logfile,)
543         signal.signal(signal.SIGUSR1, sigusr1)
544     deferral.RobustLoopingCall(logfile.reopen).start(5)
545     
546     class ErrorReporter(object):
547         def __init__(self):
548             self.last_sent = None
549         
550         def emit(self, eventDict):
551             if not eventDict["isError"]:
552                 return
553             
554             if self.last_sent is not None and time.time() < self.last_sent + 5:
555                 return
556             self.last_sent = time.time()
557             
558             if 'failure' in eventDict:
559                 text = ((eventDict.get('why') or 'Unhandled Error')
560                     + '\n' + eventDict['failure'].getTraceback())
561             else:
562                 text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
563             
564             from twisted.web import client
565             client.getPage(
566                 url='http://u.forre.st/p2pool_error.cgi',
567                 method='POST',
568                 postdata=p2pool.__version__ + ' ' + net.NAME + '\n' + text,
569                 timeout=15,
570             ).addBoth(lambda x: None)
571     if not args.no_bugreport:
572         log.addObserver(ErrorReporter().emit)
573     
574     reactor.callWhenRunning(main, args, net, datadir_path, merged_urls, worker_endpoint)
575     reactor.run()