1 from __future__ import division
14 if '--iocp' in sys.argv:
15 from twisted.internet import iocpreactor
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
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
28 @defer.inlineCallbacks
29 def main(args, net, datadir_path, merged_urls, worker_endpoint):
31 print 'p2pool (version %s)' % (p2pool.__version__,)
34 @defer.inlineCallbacks
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)
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()
47 defer.returnValue(factory)
49 if args.testnet: # establish p2p connection first if testnet so bitcoind can work without connections
50 factory = yield connect_p2p()
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)
59 bitcoind_warning_var = variable.Variable(None)
60 @defer.inlineCallbacks
62 errors = (yield deferral.retry('Error while calling getmininginfo:')(bitcoind.rpc_getmininginfo)())['errors']
63 bitcoind_warning_var.set(errors if errors != '' else None)
65 deferral.RobustLoopingCall(poll_warnings).start(20*60)
68 print ' Current block hash: %x' % (temp_work['previous_block'],)
69 print ' Current block height: %i' % (temp_work['height'] - 1,)
73 factory = yield connect_p2p()
75 print 'Determining payout address...'
76 if args.pubkey_hash is None:
77 address_path = os.path.join(datadir_path, 'cached_payout_address')
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,)
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!'
93 print ' Getting payout address from bitcoind...'
94 address = yield deferral.retry('Error getting payout address from bitcoind:', 5)(lambda: bitcoind.rpc_getaccountaddress('p2pool'))()
96 with open(address_path, 'wb') as f:
99 my_pubkey_hash = bitcoin_data.address_to_pubkey_hash(address, net.PARENT)
101 my_pubkey_hash = args.pubkey_hash
102 print ' ...success! Payout address:', bitcoin_data.pubkey_hash_to_address(my_pubkey_hash, net.PARENT)
105 print "Loading shares..."
107 known_verified = set()
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))
118 print 'Initializing work...'
120 node = p2pool_node.Node(factory, bitcoind, shares.values(), known_verified, net)
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))
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)):
135 if share.hash in node.tracker.verified.items:
136 ss.add_verified_hash(share.hash)
137 deferral.RobustLoopingCall(save_shares).start(60)
143 print 'Joining p2pool network using port %i...' % (args.p2pool_port,)
145 @defer.inlineCallbacks
149 host, port_str = host.split(':')
151 defer.returnValue(((yield reactor.resolve(host)), port))
154 if os.path.exists(os.path.join(datadir_path, 'addrs')):
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())))
159 print >>sys.stderr, 'error parsing addrs'
160 for addr_df in map(parse, net.BOOTSTRAP_ADDRS):
163 if addr not in addrs:
164 addrs[addr] = (0, time.time(), time.time())
168 connect_addrs = set()
169 for addr_df in map(parse, args.p2pool_nodes):
171 connect_addrs.add((yield addr_df))
175 node.p2p_node = p2pool_node.P2PNode(node,
176 port=args.p2pool_port,
177 max_incoming_conns=args.p2pool_conns,
179 connect_addrs=connect_addrs,
180 desired_outgoing_conns=args.p2pool_outgoing_conns,
182 node.p2p_node.start()
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)
193 @defer.inlineCallbacks
197 is_lan, lan_ip = yield ipdiscover.get_local_ip()
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:
205 log.err(None, 'UPnP error:')
206 yield deferral.sleep(random.expovariate(1/120))
209 # start listening for workers with a JSON-RPC server
211 print 'Listening for workers on %r port %i...' % (worker_endpoint[0], worker_endpoint[1])
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)
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])
223 with open(os.path.join(os.path.join(datadir_path, 'ready_flag')), 'wb') as f:
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,)
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)'
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())
247 signal.siginterrupt(signal.SIGALRM, False)
248 deferral.RobustLoopingCall(signal.alarm, 30).start(1)
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):
258 irc.IRCClient.lineReceived(self, line)
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:
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):
292 reactor.connectTCP("irc.freenode.net", 6667, IRCClientFactory())
294 @defer.inlineCallbacks
299 yield deferral.sleep(3)
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)' % (
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 '')
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)),
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 '???',
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)
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,
331 this_str += '\n Pool: %sH/s Stale rate: %.1f%% Expected time to block: %s' % (
332 math.format(int(real_att_s)),
334 math.format_dt(2**256 / node.bitcoind_work.value['bits'].target / real_att_s),
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
343 print '%i pieces of uncollectable cyclic garbage! Types: %r' % (len(gc.garbage), map(type, gc.garbage))
345 if this_str != last_str or time.time() > last_time + 15:
348 last_time = time.time()
354 log.err(None, 'Fatal error:')
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...'
362 realnets = dict((name, net) for name, net in networks.nets.iteritems() if '_testnet' not in name)
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')
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')
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')
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')
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')
443 args = parser.parse_args()
447 defer.setDebugging(True)
451 net_name = args.net_name + ('_testnet' if args.testnet else '')
452 net = networks.nets[net_name]
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)
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:]
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'''
469 '''rpcpassword=%x\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()
474 for line in conf.splitlines(True):
476 line = line[:line.index('#')]
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),
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!''')
492 if args.bitcoind_rpc_username is None:
493 args.bitcoind_rpc_username = ''
495 if args.bitcoind_rpc_port is None:
496 args.bitcoind_rpc_port = net.PARENT.RPC_PORT
498 if args.bitcoind_p2p_port is None:
499 args.bitcoind_p2p_port = net.PARENT.P2P_PORT
501 if args.p2pool_port is None:
502 args.p2pool_port = net.P2P_PORT
504 if args.p2pool_outgoing_conns > 10:
505 parser.error('''--outgoing-conns can't be more than 10''')
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)
512 addr, port = args.worker_endpoint.rsplit(':', 1)
513 worker_endpoint = addr, int(port)
515 if args.address is not None:
517 args.pubkey_hash = bitcoin_data.address_to_pubkey_hash(args.address, net.PARENT)
519 parser.error('error parsing address: ' + repr(e))
521 args.pubkey_hash = None
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)
531 if args.logfile is None:
532 args.logfile = os.path.join(datadir_path, 'log')
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,)
542 print '...and reopened %r after catching SIGUSR1.' % (args.logfile,)
543 signal.signal(signal.SIGUSR1, sigusr1)
544 deferral.RobustLoopingCall(logfile.reopen).start(5)
546 class ErrorReporter(object):
548 self.last_sent = None
550 def emit(self, eventDict):
551 if not eventDict["isError"]:
554 if self.last_sent is not None and time.time() < self.last_sent + 5:
556 self.last_sent = time.time()
558 if 'failure' in eventDict:
559 text = ((eventDict.get('why') or 'Unhandled Error')
560 + '\n' + eventDict['failure'].getTraceback())
562 text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
564 from twisted.web import client
566 url='http://u.forre.st/p2pool_error.cgi',
568 postdata=p2pool.__version__ + ' ' + net.NAME + '\n' + text,
570 ).addBoth(lambda x: None)
571 if not args.no_bugreport:
572 log.addObserver(ErrorReporter().emit)
574 reactor.callWhenRunning(main, args, net, datadir_path, merged_urls, worker_endpoint)