9c121fd7dfd96a0332c53464e16aa06602714a8e
[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_getinfo_var = variable.Variable(None)
60         @defer.inlineCallbacks
61         def poll_warnings():
62             bitcoind_getinfo_var.set((yield deferral.retry('Error while calling getinfo:')(bitcoind.rpc_getinfo)()))
63         yield poll_warnings()
64         deferral.RobustLoopingCall(poll_warnings).start(20*60)
65         
66         print '    ...success!'
67         print '    Current block hash: %x' % (temp_work['previous_block'],)
68         print '    Current block height: %i' % (temp_work['height'] - 1,)
69         print
70         
71         if not args.testnet:
72             factory = yield connect_p2p()
73         
74         print 'Determining payout address...'
75         if args.pubkey_hash is None:
76             address_path = os.path.join(datadir_path, 'cached_payout_address')
77             
78             if os.path.exists(address_path):
79                 with open(address_path, 'rb') as f:
80                     address = f.read().strip('\r\n')
81                 print '    Loaded cached address: %s...' % (address,)
82             else:
83                 address = None
84             
85             if address is not None:
86                 res = yield deferral.retry('Error validating cached address:', 5)(lambda: bitcoind.rpc_validateaddress(address))()
87                 if not res['isvalid'] or not res['ismine']:
88                     print '    Cached address is either invalid or not controlled by local bitcoind!'
89                     address = None
90             
91             if address is None:
92                 print '    Getting payout address from bitcoind...'
93                 address = yield deferral.retry('Error getting payout address from bitcoind:', 5)(lambda: bitcoind.rpc_getaccountaddress('p2pool'))()
94             
95             with open(address_path, 'wb') as f:
96                 f.write(address)
97             
98             my_pubkey_hash = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
99         else:
100             my_pubkey_hash = args.pubkey_hash
101         print '    ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT)
102         print
103         
104         print "Loading shares..."
105         shares = {}
106         known_verified = set()
107         def share_cb(share):
108             share.time_seen = 0 # XXX
109             shares[share.hash] = share
110             if len(shares) % 1000 == 0 and shares:
111                 print "    %i" % (len(shares),)
112         ss = p2pool_data.ShareStore(os.path.join(datadir_path, 'shares.'), net, share_cb, known_verified.add)
113         print "    ...done loading %i shares (%i verified)!" % (len(shares), len(known_verified))
114         print
115         
116         
117         print 'Initializing work...'
118         
119         node = p2pool_node.Node(factory, bitcoind, shares.values(), known_verified, net)
120         yield node.start()
121         
122         for share_hash in shares:
123             if share_hash not in node.tracker.items:
124                 ss.forget_share(share_hash)
125         for share_hash in known_verified:
126             if share_hash not in node.tracker.verified.items:
127                 ss.forget_verified_share(share_hash)
128         node.tracker.removed.watch(lambda share: ss.forget_share(share.hash))
129         node.tracker.verified.removed.watch(lambda share: ss.forget_verified_share(share.hash))
130         
131         def save_shares():
132             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)):
133                 ss.add_share(share)
134                 if share.hash in node.tracker.verified.items:
135                     ss.add_verified_hash(share.hash)
136         deferral.RobustLoopingCall(save_shares).start(60)
137         
138         print '    ...success!'
139         print
140         
141         
142         print 'Joining p2pool network using port %i...' % (args.p2pool_port,)
143         
144         @defer.inlineCallbacks
145         def parse(host):
146             port = net.P2P_PORT
147             if ':' in host:
148                 host, port_str = host.split(':')
149                 port = int(port_str)
150             defer.returnValue(((yield reactor.resolve(host)), port))
151         
152         addrs = {}
153         if os.path.exists(os.path.join(datadir_path, 'addrs')):
154             try:
155                 with open(os.path.join(datadir_path, 'addrs'), 'rb') as f:
156                     addrs.update(dict((tuple(k), v) for k, v in json.loads(f.read())))
157             except:
158                 print >>sys.stderr, 'error parsing addrs'
159         for addr_df in map(parse, net.BOOTSTRAP_ADDRS):
160             try:
161                 addr = yield addr_df
162                 if addr not in addrs:
163                     addrs[addr] = (0, time.time(), time.time())
164             except:
165                 log.err()
166         
167         connect_addrs = set()
168         for addr_df in map(parse, args.p2pool_nodes):
169             try:
170                 connect_addrs.add((yield addr_df))
171             except:
172                 log.err()
173         
174         node.p2p_node = p2pool_node.P2PNode(node,
175             port=args.p2pool_port,
176             max_incoming_conns=args.p2pool_conns,
177             addr_store=addrs,
178             connect_addrs=connect_addrs,
179             desired_outgoing_conns=args.p2pool_outgoing_conns,
180             advertise_ip=args.advertise_ip,
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_getinfo_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(), bindAddress=(worker_endpoint[0], 0))
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_getinfo_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     parser.add_argument('--disable-advertise',
417         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''',
418         action='store_false', default=True, dest='advertise_ip')
419     
420     worker_group = parser.add_argument_group('worker interface')
421     worker_group.add_argument('-w', '--worker-port', metavar='PORT or ADDR:PORT',
422         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())),
423         type=str, action='store', default=None, dest='worker_endpoint')
424     worker_group.add_argument('-f', '--fee', metavar='FEE_PERCENTAGE',
425         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)''',
426         type=float, action='store', default=0, dest='worker_fee')
427     
428     bitcoind_group = parser.add_argument_group('bitcoind interface')
429     bitcoind_group.add_argument('--bitcoind-address', metavar='BITCOIND_ADDRESS',
430         help='connect to this address (default: 127.0.0.1)',
431         type=str, action='store', default='127.0.0.1', dest='bitcoind_address')
432     bitcoind_group.add_argument('--bitcoind-rpc-port', metavar='BITCOIND_RPC_PORT',
433         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())),
434         type=int, action='store', default=None, dest='bitcoind_rpc_port')
435     bitcoind_group.add_argument('--bitcoind-rpc-ssl',
436         help='connect to JSON-RPC interface using SSL',
437         action='store_true', default=False, dest='bitcoind_rpc_ssl')
438     bitcoind_group.add_argument('--bitcoind-p2p-port', metavar='BITCOIND_P2P_PORT',
439         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())),
440         type=int, action='store', default=None, dest='bitcoind_p2p_port')
441     
442     bitcoind_group.add_argument(metavar='BITCOIND_RPCUSERPASS',
443         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)',
444         type=str, action='store', default=[], nargs='*', dest='bitcoind_rpc_userpass')
445     
446     args = parser.parse_args()
447     
448     if args.debug:
449         p2pool.DEBUG = True
450         defer.setDebugging(True)
451     else:
452         p2pool.DEBUG = False
453     
454     net_name = args.net_name + ('_testnet' if args.testnet else '')
455     net = networks.nets[net_name]
456     
457     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)
458     if not os.path.exists(datadir_path):
459         os.makedirs(datadir_path)
460     
461     if len(args.bitcoind_rpc_userpass) > 2:
462         parser.error('a maximum of two arguments are allowed')
463     args.bitcoind_rpc_username, args.bitcoind_rpc_password = ([None, None] + args.bitcoind_rpc_userpass)[-2:]
464     
465     if args.bitcoind_rpc_password is None:
466         conf_path = net.PARENT.CONF_FILE_FUNC()
467         if not os.path.exists(conf_path):
468             parser.error('''Bitcoin configuration file not found. Manually enter your RPC password.\r\n'''
469                 '''If you actually haven't created a configuration file, you should create one at %s with the text:\r\n'''
470                 '''\r\n'''
471                 '''server=1\r\n'''
472                 '''rpcpassword=%x\r\n'''
473                 '''\r\n'''
474                 '''Keep that password secret! After creating the file, restart Bitcoin.''' % (conf_path, random.randrange(2**128)))
475         conf = open(conf_path, 'rb').read()
476         contents = {}
477         for line in conf.splitlines(True):
478             if '#' in line:
479                 line = line[:line.index('#')]
480             if '=' not in line:
481                 continue
482             k, v = line.split('=', 1)
483             contents[k.strip()] = v.strip()
484         for conf_name, var_name, var_type in [
485             ('rpcuser', 'bitcoind_rpc_username', str),
486             ('rpcpassword', 'bitcoind_rpc_password', str),
487             ('rpcport', 'bitcoind_rpc_port', int),
488             ('port', 'bitcoind_p2p_port', int),
489         ]:
490             if getattr(args, var_name) is None and conf_name in contents:
491                 setattr(args, var_name, var_type(contents[conf_name]))
492         if args.bitcoind_rpc_password is None:
493             parser.error('''Bitcoin configuration file didn't contain an rpcpassword= line! Add one!''')
494     
495     if args.bitcoind_rpc_username is None:
496         args.bitcoind_rpc_username = ''
497     
498     if args.bitcoind_rpc_port is None:
499         args.bitcoind_rpc_port = net.PARENT.RPC_PORT
500     
501     if args.bitcoind_p2p_port is None:
502         args.bitcoind_p2p_port = net.PARENT.P2P_PORT
503     
504     if args.p2pool_port is None:
505         args.p2pool_port = net.P2P_PORT
506     
507     if args.p2pool_outgoing_conns > 10:
508         parser.error('''--outgoing-conns can't be more than 10''')
509     
510     if args.worker_endpoint is None:
511         worker_endpoint = '', net.WORKER_PORT
512     elif ':' not in args.worker_endpoint:
513         worker_endpoint = '', int(args.worker_endpoint)
514     else:
515         addr, port = args.worker_endpoint.rsplit(':', 1)
516         worker_endpoint = addr, int(port)
517     
518     if args.address is not None:
519         try:
520             args.pubkey_hash = bitcoin_data.address_to_pubkey_hash(args.address, net.PARENT)
521         except Exception, e:
522             parser.error('error parsing address: ' + repr(e))
523     else:
524         args.pubkey_hash = None
525     
526     def separate_url(url):
527         s = urlparse.urlsplit(url)
528         if '@' not in s.netloc:
529             parser.error('merged url netloc must contain an "@"')
530         userpass, new_netloc = s.netloc.rsplit('@', 1)
531         return urlparse.urlunsplit(s._replace(netloc=new_netloc)), userpass
532     merged_urls = map(separate_url, args.merged_urls)
533     
534     if args.logfile is None:
535         args.logfile = os.path.join(datadir_path, 'log')
536     
537     logfile = logging.LogFile(args.logfile)
538     pipe = logging.TimestampingPipe(logging.TeePipe([logging.EncodeReplacerPipe(sys.stderr), logfile]))
539     sys.stdout = logging.AbortPipe(pipe)
540     sys.stderr = log.DefaultObserver.stderr = logging.AbortPipe(logging.PrefixPipe(pipe, '> '))
541     if hasattr(signal, "SIGUSR1"):
542         def sigusr1(signum, frame):
543             print 'Caught SIGUSR1, closing %r...' % (args.logfile,)
544             logfile.reopen()
545             print '...and reopened %r after catching SIGUSR1.' % (args.logfile,)
546         signal.signal(signal.SIGUSR1, sigusr1)
547     deferral.RobustLoopingCall(logfile.reopen).start(5)
548     
549     class ErrorReporter(object):
550         def __init__(self):
551             self.last_sent = None
552         
553         def emit(self, eventDict):
554             if not eventDict["isError"]:
555                 return
556             
557             if self.last_sent is not None and time.time() < self.last_sent + 5:
558                 return
559             self.last_sent = time.time()
560             
561             if 'failure' in eventDict:
562                 text = ((eventDict.get('why') or 'Unhandled Error')
563                     + '\n' + eventDict['failure'].getTraceback())
564             else:
565                 text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
566             
567             from twisted.web import client
568             client.getPage(
569                 url='http://u.forre.st/p2pool_error.cgi',
570                 method='POST',
571                 postdata=p2pool.__version__ + ' ' + net.NAME + '\n' + text,
572                 timeout=15,
573             ).addBoth(lambda x: None)
574     if not args.no_bugreport:
575         log.addObserver(ErrorReporter().emit)
576     
577     reactor.callWhenRunning(main, args, net, datadir_path, merged_urls, worker_endpoint)
578     reactor.run()