5a8c79f92765f335e02a07fd8349206e0b4204b9
[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             advertise_ip=args.advertise_ip,
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         deferral.RobustLoopingCall(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         caching_wb = worker_interface.CachingWorkerBridge(wb)
217         worker_interface.WorkerInterface(caching_wb).attach_to(web_root, get_handler=lambda request: request.redirect('static/'))
218         web_serverfactory = server.Site(web_root)
219         
220         
221         serverfactory = switchprotocol.FirstByteSwitchFactory({'{': stratum.StratumServerFactory(caching_wb)}, web_serverfactory)
222         deferral.retry('Error binding to worker port:', traceback=False)(reactor.listenTCP)(worker_endpoint[1], serverfactory, interface=worker_endpoint[0])
223         
224         with open(os.path.join(os.path.join(datadir_path, 'ready_flag')), 'wb') as f:
225             pass
226         
227         print '    ...success!'
228         print
229         
230         
231         # done!
232         print 'Started successfully!'
233         print 'Go to http://127.0.0.1:%i/ to view graphs and statistics!' % (worker_endpoint[1],)
234         if args.donation_percentage > 1.1:
235             print '''Donating %.1f%% of work towards P2Pool's development. Thanks for the tip!''' % (args.donation_percentage,)
236         elif args.donation_percentage < .9:
237             print '''Donating %.1f%% of work towards P2Pool's development. Please donate to encourage further development of P2Pool!''' % (args.donation_percentage,)
238         else:
239             print '''Donating %.1f%% of work towards P2Pool's development. Thank you!''' % (args.donation_percentage,)
240             print 'You can increase this amount with --give-author argument! (or decrease it, if you must)'
241         print
242         
243         
244         if hasattr(signal, 'SIGALRM'):
245             signal.signal(signal.SIGALRM, lambda signum, frame: reactor.callFromThread(
246                 sys.stderr.write, 'Watchdog timer went off at:\n' + ''.join(traceback.format_stack())
247             ))
248             signal.siginterrupt(signal.SIGALRM, False)
249             deferral.RobustLoopingCall(signal.alarm, 30).start(1)
250         
251         if args.irc_announce:
252             from twisted.words.protocols import irc
253             class IRCClient(irc.IRCClient):
254                 nickname = 'p2pool%02i' % (random.randrange(100),)
255                 channel = net.ANNOUNCE_CHANNEL
256                 def lineReceived(self, line):
257                     if p2pool.DEBUG:
258                         print repr(line)
259                     irc.IRCClient.lineReceived(self, line)
260                 def signedOn(self):
261                     self.in_channel = False
262                     irc.IRCClient.signedOn(self)
263                     self.factory.resetDelay()
264                     self.join(self.channel)
265                     @defer.inlineCallbacks
266                     def new_share(share):
267                         if not self.in_channel:
268                             return
269                         if share.pow_hash <= share.header['bits'].target and abs(share.timestamp - time.time()) < 10*60:
270                             yield deferral.sleep(random.expovariate(1/60))
271                             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)
272                             if all('%x' % (share.header_hash,) not in old_message for old_message in self.recent_messages):
273                                 self.say(self.channel, message)
274                                 self._remember_message(message)
275                     self.watch_id = node.tracker.verified.added.watch(new_share)
276                     self.recent_messages = []
277                 def joined(self, channel):
278                     self.in_channel = True
279                 def left(self, channel):
280                     self.in_channel = False
281                 def _remember_message(self, message):
282                     self.recent_messages.append(message)
283                     while len(self.recent_messages) > 100:
284                         self.recent_messages.pop(0)
285                 def privmsg(self, user, channel, message):
286                     if channel == self.channel:
287                         self._remember_message(message)
288                 def connectionLost(self, reason):
289                     node.tracker.verified.added.unwatch(self.watch_id)
290                     print 'IRC connection lost:', reason.getErrorMessage()
291             class IRCClientFactory(protocol.ReconnectingClientFactory):
292                 protocol = IRCClient
293             reactor.connectTCP("irc.freenode.net", 6667, IRCClientFactory())
294         
295         @defer.inlineCallbacks
296         def status_thread():
297             last_str = None
298             last_time = 0
299             while True:
300                 yield deferral.sleep(3)
301                 try:
302                     height = node.tracker.get_height(node.best_share_var.value)
303                     this_str = 'P2Pool: %i shares in chain (%i verified/%i total) Peers: %i (%i incoming)' % (
304                         height,
305                         len(node.tracker.verified.items),
306                         len(node.tracker.items),
307                         len(node.p2p_node.peers),
308                         sum(1 for peer in node.p2p_node.peers.itervalues() if peer.incoming),
309                     ) + (' FDs: %i R/%i W' % (len(reactor.getReaders()), len(reactor.getWriters())) if p2pool.DEBUG else '')
310                     
311                     datums, dt = wb.local_rate_monitor.get_datums_in_last()
312                     my_att_s = sum(datum['work']/dt for datum in datums)
313                     my_shares_per_s = sum(datum['work']/dt/bitcoin_data.target_to_average_attempts(datum['share_target']) for datum in datums)
314                     this_str += '\n Local: %sH/s in last %s Local dead on arrival: %s Expected time to share: %s' % (
315                         math.format(int(my_att_s)),
316                         math.format_dt(dt),
317                         math.format_binomial_conf(sum(1 for datum in datums if datum['dead']), len(datums), 0.95),
318                         math.format_dt(1/my_shares_per_s) if my_shares_per_s else '???',
319                     )
320                     
321                     if height > 2:
322                         (stale_orphan_shares, stale_doa_shares), shares, _ = wb.get_stale_counts()
323                         stale_prop = p2pool_data.get_average_stale_prop(node.tracker, node.best_share_var.value, min(60*60//net.SHARE_PERIOD, height))
324                         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)
325                         
326                         this_str += '\n Shares: %i (%i orphan, %i dead) Stale rate: %s Efficiency: %s Current payout: %.4f %s' % (
327                             shares, stale_orphan_shares, stale_doa_shares,
328                             math.format_binomial_conf(stale_orphan_shares + stale_doa_shares, shares, 0.95),
329                             math.format_binomial_conf(stale_orphan_shares + stale_doa_shares, shares, 0.95, lambda x: (1 - x)/(1 - stale_prop)),
330                             node.get_current_txouts().get(bitcoin_data.pubkey_hash_to_script2(my_pubkey_hash), 0)*1e-8, net.PARENT.SYMBOL,
331                         )
332                         this_str += '\n Pool: %sH/s Stale rate: %.1f%% Expected time to block: %s' % (
333                             math.format(int(real_att_s)),
334                             100*stale_prop,
335                             math.format_dt(2**256 / node.bitcoind_work.value['bits'].target / real_att_s),
336                         )
337                         
338                         for warning in p2pool_data.get_warnings(node.tracker, node.best_share_var.value, net, bitcoind_warning_var.value, node.bitcoind_work.value):
339                             print >>sys.stderr, '#'*40
340                             print >>sys.stderr, '>>> Warning: ' + warning
341                             print >>sys.stderr, '#'*40
342                         
343                         if gc.garbage:
344                             print '%i pieces of uncollectable cyclic garbage! Types: %r' % (len(gc.garbage), map(type, gc.garbage))
345                     
346                     if this_str != last_str or time.time() > last_time + 15:
347                         print this_str
348                         last_str = this_str
349                         last_time = time.time()
350                 except:
351                     log.err()
352         status_thread()
353     except:
354         reactor.stop()
355         log.err(None, 'Fatal error:')
356
357 def run():
358     if not hasattr(tcp.Client, 'abortConnection'):
359         print "Twisted doesn't have abortConnection! Upgrade to a newer version of Twisted to avoid memory leaks!"
360         print 'Pausing for 3 seconds...'
361         time.sleep(3)
362     
363     realnets = dict((name, net) for name, net in networks.nets.iteritems() if '_testnet' not in name)
364     
365     parser = fixargparse.FixedArgumentParser(description='p2pool (version %s)' % (p2pool.__version__,), fromfile_prefix_chars='@')
366     parser.add_argument('--version', action='version', version=p2pool.__version__)
367     parser.add_argument('--net',
368         help='use specified network (default: bitcoin)',
369         action='store', choices=sorted(realnets), default='bitcoin', dest='net_name')
370     parser.add_argument('--testnet',
371         help='''use the network's testnet''',
372         action='store_const', const=True, default=False, dest='testnet')
373     parser.add_argument('--debug',
374         help='enable debugging mode',
375         action='store_const', const=True, default=False, dest='debug')
376     parser.add_argument('-a', '--address',
377         help='generate payouts to this address (default: <address requested from bitcoind>)',
378         type=str, action='store', default=None, dest='address')
379     parser.add_argument('--datadir',
380         help='store data in this directory (default: <directory run_p2pool.py is in>/data)',
381         type=str, action='store', default=None, dest='datadir')
382     parser.add_argument('--logfile',
383         help='''log to this file (default: data/<NET>/log)''',
384         type=str, action='store', default=None, dest='logfile')
385     parser.add_argument('--merged',
386         help='call getauxblock on this url to get work for merged mining (example: http://ncuser:ncpass@127.0.0.1:10332/)',
387         type=str, action='append', default=[], dest='merged_urls')
388     parser.add_argument('--give-author', metavar='DONATION_PERCENTAGE',
389         help='donate this percentage of work towards the development of p2pool (default: 1.0)',
390         type=float, action='store', default=1.0, dest='donation_percentage')
391     parser.add_argument('--iocp',
392         help='use Windows IOCP API in order to avoid errors due to large number of sockets being open',
393         action='store_true', default=False, dest='iocp')
394     parser.add_argument('--irc-announce',
395         help='announce any blocks found on irc://irc.freenode.net/#p2pool',
396         action='store_true', default=False, dest='irc_announce')
397     parser.add_argument('--no-bugreport',
398         help='disable submitting caught exceptions to the author',
399         action='store_true', default=False, dest='no_bugreport')
400     
401     p2pool_group = parser.add_argument_group('p2pool interface')
402     p2pool_group.add_argument('--p2pool-port', metavar='PORT',
403         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())),
404         type=int, action='store', default=None, dest='p2pool_port')
405     p2pool_group.add_argument('-n', '--p2pool-node', metavar='ADDR[:PORT]',
406         help='connect to existing p2pool node at ADDR listening on port PORT (defaults to default p2pool P2P port) in addition to builtin addresses',
407         type=str, action='append', default=[], dest='p2pool_nodes')
408     parser.add_argument('--disable-upnp',
409         help='''don't attempt to use UPnP to forward p2pool's P2P port from the Internet to this computer''',
410         action='store_false', default=True, dest='upnp')
411     p2pool_group.add_argument('--max-conns', metavar='CONNS',
412         help='maximum incoming connections (default: 40)',
413         type=int, action='store', default=40, dest='p2pool_conns')
414     p2pool_group.add_argument('--outgoing-conns', metavar='CONNS',
415         help='outgoing connections (default: 6)',
416         type=int, action='store', default=6, dest='p2pool_outgoing_conns')
417     parser.add_argument('--disable-advertise',
418         help='''don't advertise local IP address as being available for incoming connections. useful for running a dark node, along with multiple -n ADDR's and --outgoing-conns 0''',
419         action='store_false', default=True, dest='advertise_ip')
420     
421     worker_group = parser.add_argument_group('worker interface')
422     worker_group.add_argument('-w', '--worker-port', metavar='PORT or ADDR:PORT',
423         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())),
424         type=str, action='store', default=None, dest='worker_endpoint')
425     worker_group.add_argument('-f', '--fee', metavar='FEE_PERCENTAGE',
426         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)''',
427         type=float, action='store', default=0, dest='worker_fee')
428     
429     bitcoind_group = parser.add_argument_group('bitcoind interface')
430     bitcoind_group.add_argument('--bitcoind-address', metavar='BITCOIND_ADDRESS',
431         help='connect to this address (default: 127.0.0.1)',
432         type=str, action='store', default='127.0.0.1', dest='bitcoind_address')
433     bitcoind_group.add_argument('--bitcoind-rpc-port', metavar='BITCOIND_RPC_PORT',
434         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())),
435         type=int, action='store', default=None, dest='bitcoind_rpc_port')
436     bitcoind_group.add_argument('--bitcoind-rpc-ssl',
437         help='connect to JSON-RPC interface using SSL',
438         action='store_true', default=False, dest='bitcoind_rpc_ssl')
439     bitcoind_group.add_argument('--bitcoind-p2p-port', metavar='BITCOIND_P2P_PORT',
440         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())),
441         type=int, action='store', default=None, dest='bitcoind_p2p_port')
442     
443     bitcoind_group.add_argument(metavar='BITCOIND_RPCUSERPASS',
444         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)',
445         type=str, action='store', default=[], nargs='*', dest='bitcoind_rpc_userpass')
446     
447     args = parser.parse_args()
448     
449     if args.debug:
450         p2pool.DEBUG = True
451         defer.setDebugging(True)
452     else:
453         p2pool.DEBUG = False
454     
455     net_name = args.net_name + ('_testnet' if args.testnet else '')
456     net = networks.nets[net_name]
457     
458     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)
459     if not os.path.exists(datadir_path):
460         os.makedirs(datadir_path)
461     
462     if len(args.bitcoind_rpc_userpass) > 2:
463         parser.error('a maximum of two arguments are allowed')
464     args.bitcoind_rpc_username, args.bitcoind_rpc_password = ([None, None] + args.bitcoind_rpc_userpass)[-2:]
465     
466     if args.bitcoind_rpc_password is None:
467         conf_path = net.PARENT.CONF_FILE_FUNC()
468         if not os.path.exists(conf_path):
469             parser.error('''Bitcoin configuration file not found. Manually enter your RPC password.\r\n'''
470                 '''If you actually haven't created a configuration file, you should create one at %s with the text:\r\n'''
471                 '''\r\n'''
472                 '''server=1\r\n'''
473                 '''rpcpassword=%x\r\n'''
474                 '''\r\n'''
475                 '''Keep that password secret! After creating the file, restart Bitcoin.''' % (conf_path, random.randrange(2**128)))
476         conf = open(conf_path, 'rb').read()
477         contents = {}
478         for line in conf.splitlines(True):
479             if '#' in line:
480                 line = line[:line.index('#')]
481             if '=' not in line:
482                 continue
483             k, v = line.split('=', 1)
484             contents[k.strip()] = v.strip()
485         for conf_name, var_name, var_type in [
486             ('rpcuser', 'bitcoind_rpc_username', str),
487             ('rpcpassword', 'bitcoind_rpc_password', str),
488             ('rpcport', 'bitcoind_rpc_port', int),
489             ('port', 'bitcoind_p2p_port', int),
490         ]:
491             if getattr(args, var_name) is None and conf_name in contents:
492                 setattr(args, var_name, var_type(contents[conf_name]))
493         if args.bitcoind_rpc_password is None:
494             parser.error('''Bitcoin configuration file didn't contain an rpcpassword= line! Add one!''')
495     
496     if args.bitcoind_rpc_username is None:
497         args.bitcoind_rpc_username = ''
498     
499     if args.bitcoind_rpc_port is None:
500         args.bitcoind_rpc_port = net.PARENT.RPC_PORT
501     
502     if args.bitcoind_p2p_port is None:
503         args.bitcoind_p2p_port = net.PARENT.P2P_PORT
504     
505     if args.p2pool_port is None:
506         args.p2pool_port = net.P2P_PORT
507     
508     if args.p2pool_outgoing_conns > 10:
509         parser.error('''--outgoing-conns can't be more than 10''')
510     
511     if args.worker_endpoint is None:
512         worker_endpoint = '', net.WORKER_PORT
513     elif ':' not in args.worker_endpoint:
514         worker_endpoint = '', int(args.worker_endpoint)
515     else:
516         addr, port = args.worker_endpoint.rsplit(':', 1)
517         worker_endpoint = addr, int(port)
518     
519     if args.address is not None:
520         try:
521             args.pubkey_hash = bitcoin_data.address_to_pubkey_hash(args.address, net.PARENT)
522         except Exception, e:
523             parser.error('error parsing address: ' + repr(e))
524     else:
525         args.pubkey_hash = None
526     
527     def separate_url(url):
528         s = urlparse.urlsplit(url)
529         if '@' not in s.netloc:
530             parser.error('merged url netloc must contain an "@"')
531         userpass, new_netloc = s.netloc.rsplit('@', 1)
532         return urlparse.urlunsplit(s._replace(netloc=new_netloc)), userpass
533     merged_urls = map(separate_url, args.merged_urls)
534     
535     if args.logfile is None:
536         args.logfile = os.path.join(datadir_path, 'log')
537     
538     logfile = logging.LogFile(args.logfile)
539     pipe = logging.TimestampingPipe(logging.TeePipe([logging.EncodeReplacerPipe(sys.stderr), logfile]))
540     sys.stdout = logging.AbortPipe(pipe)
541     sys.stderr = log.DefaultObserver.stderr = logging.AbortPipe(logging.PrefixPipe(pipe, '> '))
542     if hasattr(signal, "SIGUSR1"):
543         def sigusr1(signum, frame):
544             print 'Caught SIGUSR1, closing %r...' % (args.logfile,)
545             logfile.reopen()
546             print '...and reopened %r after catching SIGUSR1.' % (args.logfile,)
547         signal.signal(signal.SIGUSR1, sigusr1)
548     deferral.RobustLoopingCall(logfile.reopen).start(5)
549     
550     class ErrorReporter(object):
551         def __init__(self):
552             self.last_sent = None
553         
554         def emit(self, eventDict):
555             if not eventDict["isError"]:
556                 return
557             
558             if self.last_sent is not None and time.time() < self.last_sent + 5:
559                 return
560             self.last_sent = time.time()
561             
562             if 'failure' in eventDict:
563                 text = ((eventDict.get('why') or 'Unhandled Error')
564                     + '\n' + eventDict['failure'].getTraceback())
565             else:
566                 text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
567             
568             from twisted.web import client
569             client.getPage(
570                 url='http://u.forre.st/p2pool_error.cgi',
571                 method='POST',
572                 postdata=p2pool.__version__ + ' ' + net.NAME + '\n' + text,
573                 timeout=15,
574             ).addBoth(lambda x: None)
575     if not args.no_bugreport:
576         log.addObserver(ErrorReporter().emit)
577     
578     reactor.callWhenRunning(main, args, net, datadir_path, merged_urls, worker_endpoint)
579     reactor.run()