2 # Eloipool - Python Bitcoin pool server
3 # Copyright (C) 2011-2013 Luke Dashjr <luke-jr+eloipool@utopios.org>
4 # Portions written by Peter Leurs <kinlo@triplemining.com>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 argparser = argparse.ArgumentParser()
22 argparser.add_argument('-c', '--config', help='Config name to load from config_<ARG>.py')
23 args = argparser.parse_args()
25 if not args.config is None:
26 configmod = 'config_%s' % (args.config,)
28 config = importlib.import_module(configmod)
30 if not hasattr(config, 'ServerName'):
31 config.ServerName = 'Unnamed Eloipool'
33 if not hasattr(config, 'ShareTarget'):
34 config.ShareTarget = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
38 import logging.handlers
40 rootlogger = logging.getLogger(None)
41 logformat = getattr(config, 'LogFormat', '%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s')
42 logformatter = logging.Formatter(logformat)
43 if len(rootlogger.handlers) == 0:
55 'Waker for JSONRPCServer',
56 'Waker for StratumServer',
59 logging.getLogger(infoOnly).setLevel(logging.INFO)
60 if getattr(config, 'LogToSysLog', False):
61 sysloghandler = logging.handlers.SysLogHandler(address = '/dev/log')
62 rootlogger.addHandler(sysloghandler)
63 if hasattr(config, 'LogFile'):
64 if isinstance(config.LogFile, str):
65 filehandler = logging.FileHandler(config.LogFile)
67 filehandler = logging.handlers.TimedRotatingFileHandler(**config.LogFile)
68 filehandler.setFormatter(logformatter)
69 rootlogger.addHandler(filehandler)
71 def RaiseRedFlags(reason):
72 logging.getLogger('redflag').critical(reason)
76 from bitcoin.node import BitcoinLink, BitcoinNode
77 bcnode = BitcoinNode(config.UpstreamNetworkId)
78 bcnode.userAgent += b'Eloipool:0.1/'
79 bcnode.newBlock = lambda blkhash: MM.updateMerkleTree()
84 import jsonrpc.authproxy
85 jsonrpc.authproxy.USER_AGENT = 'Eloipool/0.1'
90 from bitcoin.script import BitcoinScript
91 from bitcoin.txn import Txn
92 from base58 import b58decode
93 from binascii import b2a_hex
94 from struct import pack
98 def makeCoinbaseTxn(coinbaseValue, useCoinbaser = True, prevBlockHex = None):
101 if useCoinbaser and hasattr(config, 'CoinbaserCmd') and config.CoinbaserCmd:
104 cmd = config.CoinbaserCmd
105 cmd = cmd.replace('%d', str(coinbaseValue))
106 cmd = cmd.replace('%p', prevBlockHex or '""')
107 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
108 nout = int(p.stdout.readline())
109 for i in range(nout):
110 amount = int(p.stdout.readline())
111 addr = p.stdout.readline().rstrip(b'\n').decode('utf8')
112 pkScript = BitcoinScript.toAddress(addr)
113 txn.addOutput(amount, pkScript)
116 coinbased = coinbaseValue + 1
117 if coinbased >= coinbaseValue:
118 logging.getLogger('makeCoinbaseTxn').error('Coinbaser failed!')
121 coinbaseValue -= coinbased
123 pkScript = BitcoinScript.toAddress(config.TrackerAddr)
124 txn.addOutput(coinbaseValue, pkScript)
127 # TODO: red flag on dupe coinbase
131 import jsonrpc_getwork
132 from util import Bits2Target
142 server.wakeLongpoll()
143 stratumsrv.updateJob()
146 global MM, networkTarget, server
147 bits = MM.currentBlock[2]
151 networkTarget = Bits2Target(bits)
152 if MM.lastBlock != (None, None, None):
155 jsonrpc_getwork._CheckForDupesHACK = {}
157 server.wakeLongpoll(wantClear=True)
158 stratumsrv.updateJob(wantClear=True)
161 from time import sleep, time
164 def _WorkLogPruner_I(wl):
168 userwork = wl[username]
169 for wli in tuple(userwork.keys()):
170 if now > userwork[wli][1] + 120:
173 WorkLogPruner.logger.debug('Pruned %d jobs' % (pruned,))
175 def WorkLogPruner(wl):
181 WorkLogPruner.logger.error(traceback.format_exc())
182 WorkLogPruner.logger = logging.getLogger('WorkLogPruner')
185 from merklemaker import merkleMaker
187 MM.__dict__.update(config.__dict__)
188 MM.makeCoinbaseTxn = makeCoinbaseTxn
189 MM.onBlockChange = blockChanged
190 MM.onBlockUpdate = updateBlocks
193 from binascii import b2a_hex
194 from copy import deepcopy
195 from math import ceil, log
196 from merklemaker import MakeBlockHeader
197 from struct import pack, unpack
199 from time import time
200 from util import PendingUpstream, RejectedShare, bdiff1target, dblsha, LEhash2int, swap32, target2bdiff, target2pdiff
205 if hasattr(config, 'GotWorkURI'):
206 gotwork = jsonrpc.ServiceProxy(config.GotWorkURI)
208 if not hasattr(config, 'DelayLogForUpstream'):
209 config.DelayLogForUpstream = False
211 if not hasattr(config, 'DynamicTargetting'):
212 config.DynamicTargetting = 0
214 if not hasattr(config, 'DynamicTargetWindow'):
215 config.DynamicTargetWindow = 120
216 config.DynamicTargetGoal *= config.DynamicTargetWindow / 60
218 def submitGotwork(info):
220 gotwork.gotwork(info)
222 checkShare.logger.warning('Failed to submit gotwork\n' + traceback.format_exc())
224 def clampTarget(target, DTMode):
225 # ShareTarget is the minimum
226 if target is None or target > config.ShareTarget:
227 target = config.ShareTarget
229 # Never target above the network, as we'd lose blocks
230 if target < networkTarget:
231 target = networkTarget
234 # Ceil target to a power of two :)
235 truebits = log(target, 2)
236 if target <= 2**int(truebits):
237 # Workaround for bug in Python's math.log function
238 truebits = int(truebits)
239 target = 2**ceil(truebits) - 1
241 # Round target to multiple of bdiff 1
242 target = bdiff1target / int(round(target2bdiff(target)))
244 # Return None for ShareTarget to save memory
245 if target == config.ShareTarget:
249 def getTarget(username, now, DTMode = None, RequestedTarget = None):
251 DTMode = config.DynamicTargetting
254 if username in userStatus:
255 status = userStatus[username]
257 # No record, use default target
258 RequestedTarget = clampTarget(RequestedTarget, DTMode)
259 userStatus[username] = [RequestedTarget, now, 0]
260 return RequestedTarget
261 (targetIn, lastUpdate, work) = status
262 if work <= config.DynamicTargetGoal:
263 if now < lastUpdate + config.DynamicTargetWindow and (targetIn is None or targetIn >= networkTarget):
264 # No reason to change it just yet
265 return clampTarget(targetIn, DTMode)
267 # No shares received, reset to minimum
269 getTarget.logger.debug("No shares from %s, resetting to minimum target" % (repr(username),))
270 userStatus[username] = [None, now, 0]
271 return clampTarget(None, DTMode)
273 deltaSec = now - lastUpdate
274 target = targetIn or config.ShareTarget
275 target = int(target * config.DynamicTargetGoal * deltaSec / config.DynamicTargetWindow / work)
276 target = clampTarget(target, DTMode)
277 if target != targetIn:
278 pfx = 'Retargetting %s' % (repr(username),)
279 tin = targetIn or config.ShareTarget
280 getTarget.logger.debug("%s from: %064x (pdiff %s)" % (pfx, tin, target2pdiff(tin)))
281 tgt = target or config.ShareTarget
282 getTarget.logger.debug("%s to: %064x (pdiff %s)" % (pfx, tgt, target2pdiff(tgt)))
283 userStatus[username] = [target, now, 0]
285 getTarget.logger = logging.getLogger('getTarget')
287 def TopTargets(n = 0x10):
288 tmp = list(k for k, v in userStatus.items() if not v[0] is None)
289 tmp.sort(key=lambda k: -userStatus[k][0])
293 tmp2[t] = target2pdiff(t)
296 tgt = userStatus[k][0]
297 print('%-34s %064x %3d' % (k, tgt, t2d(tgt)))
299 def RegisterWork(username, wli, wld, RequestedTarget = None):
301 target = getTarget(username, now, RequestedTarget=RequestedTarget)
302 wld = tuple(wld) + (target,)
303 workLog.setdefault(username, {})[wli] = (wld, now)
304 return target or config.ShareTarget
306 def getBlockHeader(username):
309 hdr = MakeBlockHeader(MRD)
310 workLog.setdefault(username, {})[merkleRoot] = (MRD, time())
311 target = RegisterWork(username, merkleRoot, MRD)
312 return (hdr, workLog[username][merkleRoot], target)
314 def getBlockTemplate(username, p_magic = None, RequestedTarget = None):
315 if server.tls.wantClear:
317 elif p_magic and username not in workLog:
322 MC = MM.getMC(wantClear)
323 (dummy, merkleTree, coinbase, prevBlock, bits) = MC[:5]
324 wliPos = coinbase[0] + 2
325 wliLen = coinbase[wliPos - 1]
326 wli = coinbase[wliPos:wliPos+wliLen]
327 target = RegisterWork(username, wli, MC, RequestedTarget=RequestedTarget)
328 return (MC, workLog[username][wli], target)
330 def getStratumJob(jobid, wantClear = False):
331 MC = MM.getMC(wantClear)
332 (dummy, merkleTree, coinbase, prevBlock, bits) = MC[:5]
334 workLog.setdefault(None, {})[jobid] = (MC, now)
335 return (MC, workLog[None][jobid])
337 def getExistingStratumJob(jobid):
338 wld = workLog[None][jobid]
347 from bitcoin.varlen import varlenEncode, varlenDecode
349 from merklemaker import assembleBlock
351 if not hasattr(config, 'BlockSubmissions'):
352 config.BlockSubmissions = None
355 def blockSubmissionThread(payload, blkhash, share):
356 if config.BlockSubmissions is None:
357 servers = list(a for b in MM.TemplateSources for a in b)
359 servers = list(config.BlockSubmissions)
361 if hasattr(share['merkletree'], 'source_uri'):
363 'access': jsonrpc.ServiceProxy(share['merkletree'].source_uri),
364 'name': share['merkletree'].source,
367 servers = list(a for b in MM.TemplateSources for a in b)
369 myblock = (blkhash, payload[4:36])
370 payload = b2a_hex(payload).decode('ascii')
377 UpstreamBitcoindJSONRPC = TS['access']
379 # BIP 22 standard submitblock
380 reason = UpstreamBitcoindJSONRPC.submitblock(payload)
381 except BaseException as gbterr:
382 gbterr_fmt = traceback.format_exc()
385 # bitcoind 0.5/0.6 getmemorypool
386 reason = UpstreamBitcoindJSONRPC.getmemorypool(payload)
388 # Old BIP 22 draft getmemorypool
389 reason = UpstreamBitcoindJSONRPC.getmemorypool(payload, {})
392 elif reason is False:
394 except BaseException as gmperr:
397 # FIXME: This will show "Method not found" on pre-BIP22 servers
398 RaiseRedFlags(gbterr_fmt)
400 if MM.currentBlock[0] not in myblock and tries > len(servers):
401 RBFs.append( (('next block', MM.currentBlock, now, (gbterr, gmperr)), payload, blkhash, share) )
402 RaiseRedFlags('Giving up on submitting block to upstream \'%s\'' % (TS['name'],))
403 if share['upstreamRejectReason'] is PendingUpstream:
404 share['upstreamRejectReason'] = 'GAVE UP'
405 share['upstreamResult'] = False
412 # At this point, we have a reason back
414 # FIXME: The returned value could be a list of multiple responses
415 msg = 'Upstream \'%s\' block submission failed: %s' % (TS['name'], reason,)
416 if success and reason in ('stale-prevblk', 'bad-prevblk', 'orphan', 'duplicate'):
418 blockSubmissionThread.logger.debug(msg)
420 RBFs.append( (('upstream reject', reason, time()), payload, blkhash, share) )
423 blockSubmissionThread.logger.debug('Upstream \'%s\' accepted block' % (TS['name'],))
425 if share['upstreamRejectReason'] is PendingUpstream:
426 share['upstreamRejectReason'] = reason
427 share['upstreamResult'] = not reason
429 blockSubmissionThread.logger = logging.getLogger('blockSubmission')
431 def checkData(share):
434 (prevBlock, height, bits) = MM.currentBlock
435 sharePrevBlock = data[4:36]
436 if sharePrevBlock != prevBlock:
437 if sharePrevBlock == MM.lastBlock[0]:
438 raise RejectedShare('stale-prevblk')
439 raise RejectedShare('bad-prevblk')
441 if data[72:76] != bits:
442 raise RejectedShare('bad-diffbits')
444 # Note that we should accept miners reducing version to 1 if they don't understand 2 yet
445 # FIXME: When the supermajority is upgraded to version 2, stop accepting 1!
446 if data[1:4] != b'\0\0\0' or data[0] > 2:
447 raise RejectedShare('bad-version')
449 def buildStratumData(share, merkleroot):
450 (prevBlock, height, bits) = MM.currentBlock
455 data += share['ntime'][::-1]
457 data += share['nonce'][::-1]
462 def IsJobValid(wli, wluser = None):
463 if wluser not in workLog:
465 if wli not in workLog[wluser]:
467 (wld, issueT) = workLog[wluser][wli]
468 if time() < issueT - 120:
472 def checkShare(share):
473 shareTime = share['time'] = time()
475 username = share['username']
481 if username not in workLog:
482 raise RejectedShare('unknown-user')
483 MWL = workLog[username]
485 shareMerkleRoot = data[36:68]
486 if 'blkdata' in share:
487 pl = share['blkdata']
488 (txncount, pl) = varlenDecode(pl)
489 cbtxn = bitcoin.txn.Txn(pl)
490 othertxndata = cbtxn.disassemble(retExtra=True)
491 coinbase = cbtxn.getCoinbase()
492 wliPos = coinbase[0] + 2
493 wliLen = coinbase[wliPos - 1]
494 wli = coinbase[wliPos:wliPos+wliLen]
498 wli = shareMerkleRoot
506 buildStratumData(share, b'\0' * 32)
512 raise RejectedShare('unknown-work')
513 (wld, issueT) = MWL[wli]
516 share['issuetime'] = issueT
518 (workMerkleTree, workCoinbase) = wld[1:3]
519 share['merkletree'] = workMerkleTree
521 cbtxn = deepcopy(workMerkleTree.data[0])
522 coinbase = workCoinbase + share['extranonce1'] + share['extranonce2']
523 cbtxn.setCoinbase(coinbase)
525 data = buildStratumData(share, workMerkleTree.withFirst(cbtxn))
526 shareMerkleRoot = data[36:68]
528 if data in DupeShareHACK:
529 raise RejectedShare('duplicate')
530 DupeShareHACK[data] = None
532 blkhash = dblsha(data)
533 if blkhash[28:] != b'\0\0\0\0':
534 raise RejectedShare('H-not-zero')
535 blkhashn = LEhash2int(blkhash)
538 logfunc = getattr(checkShare.logger, 'info' if blkhashn <= networkTarget else 'debug')
539 logfunc('BLKHASH: %64x' % (blkhashn,))
540 logfunc(' TARGET: %64x' % (networkTarget,))
542 # NOTE: this isn't actually needed for MC mode, but we're abusing it for a trivial share check...
543 txlist = workMerkleTree.data
544 txlist = [deepcopy(txlist[0]),] + txlist[1:]
546 cbtxn.setCoinbase(coinbase or workCoinbase)
549 if blkhashn <= networkTarget:
550 logfunc("Submitting upstream")
551 RBDs.append( deepcopy( (data, txlist, share.get('blkdata', None), workMerkleTree, share, wld) ) )
553 payload = assembleBlock(data, txlist)
555 payload = share['data']
556 if len(othertxndata):
557 payload += share['blkdata']
559 payload += assembleBlock(data, txlist)[80:]
560 logfunc('Real block payload: %s' % (b2a_hex(payload).decode('utf8'),))
562 threading.Thread(target=blockSubmissionThread, args=(payload, blkhash, share)).start()
563 bcnode.submitBlock(payload)
564 if config.DelayLogForUpstream:
565 share['upstreamRejectReason'] = PendingUpstream
567 share['upstreamRejectReason'] = None
568 share['upstreamResult'] = True
569 MM.updateBlock(blkhash)
572 if gotwork and blkhashn <= config.GotWorkTarget:
574 coinbaseMrkl = cbtxn.data
575 coinbaseMrkl += blkhash
576 steps = workMerkleTree._steps
577 coinbaseMrkl += pack('B', len(steps))
580 coinbaseMrkl += b"\0\0\0\0"
582 info['hash'] = b2a_hex(blkhash).decode('ascii')
583 info['header'] = b2a_hex(data).decode('ascii')
584 info['coinbaseMrkl'] = b2a_hex(coinbaseMrkl).decode('ascii')
585 thr = threading.Thread(target=submitGotwork, args=(info,))
589 checkShare.logger.warning('Failed to build gotwork request')
591 if 'target' in share:
592 workTarget = share['target']
598 if workTarget is None:
599 workTarget = config.ShareTarget
600 if blkhashn > workTarget:
601 raise RejectedShare('high-hash')
602 share['target'] = workTarget
603 share['_targethex'] = '%064x' % (workTarget,)
605 shareTimestamp = unpack('<L', data[68:72])[0]
606 if shareTime < issueT - 120:
607 raise RejectedShare('stale-work')
608 if shareTimestamp < shareTime - 300:
609 raise RejectedShare('time-too-old')
610 if shareTimestamp > shareTime + 7200:
611 raise RejectedShare('time-too-new')
613 if config.DynamicTargetting and username in userStatus:
614 # NOTE: userStatus[username] only doesn't exist across restarts
615 status = userStatus[username]
616 target = status[0] or config.ShareTarget
617 if target == workTarget:
618 userStatus[username][2] += 1
620 userStatus[username][2] += float(target) / workTarget
624 cbpreLen = len(cbpre)
625 if coinbase[:cbpreLen] != cbpre:
626 raise RejectedShare('bad-cb-prefix')
628 # Filter out known "I support" flags, to prevent exploits
629 for ff in (b'/P2SH/', b'NOP2SH', b'p2sh/CHV', b'p2sh/NOCHV'):
630 if coinbase.find(ff) > max(-1, cbpreLen - len(ff)):
631 raise RejectedShare('bad-cb-flag')
633 if len(coinbase) > 100:
634 raise RejectedShare('bad-cb-length')
636 if shareMerkleRoot != workMerkleTree.withFirst(cbtxn):
637 raise RejectedShare('bad-txnmrklroot')
639 if len(othertxndata):
640 allowed = assembleBlock(data, txlist)[80:]
641 if allowed != share['blkdata']:
642 raise RejectedShare('bad-txns')
643 checkShare.logger = logging.getLogger('checkShare')
646 if '_origdata' in share:
647 share['solution'] = share['_origdata']
649 share['solution'] = b2a_hex(swap32(share['data'])).decode('utf8')
650 for i in loggersShare:
653 def checkAuthentication(username, password):
654 # HTTPServer uses bytes, and StratumServer uses str
655 if hasattr(username, 'decode'): username = username.decode('utf8')
656 if hasattr(password, 'decode'): password = password.decode('utf8')
658 for i in authenticators:
659 if i.checkAuthentication(username, password):
663 def receiveShare(share):
664 # TODO: username => userid
667 except RejectedShare as rej:
668 share['rejectReason'] = str(rej)
670 except BaseException as e:
671 share['rejectReason'] = 'ERROR'
674 if not share.get('upstreamRejectReason', None) is PendingUpstream:
677 def newBlockNotification():
678 logging.getLogger('newBlockNotification').info('Received new block notification')
679 MM.updateMerkleTree()
680 # TODO: Force RESPOND TO LONGPOLLS?
683 def newBlockNotificationSIGNAL(signum, frame):
684 # Use a new thread, in case the signal handler is called with locks held
685 thr = threading.Thread(target=newBlockNotification, name='newBlockNotification via signal %s' % (signum,))
689 from signal import signal, SIGUSR1
690 signal(SIGUSR1, newBlockNotificationSIGNAL)
698 from time import sleep
701 if getattr(config, 'SaveStateFilename', None) is None:
702 config.SaveStateFilename = 'eloipool.worklog'
705 logger = logging.getLogger('stopServers')
707 if hasattr(stopServers, 'already'):
708 logger.debug('Already tried to stop servers before')
710 stopServers.already = True
712 logger.info('Stopping servers...')
713 global bcnode, server
714 servers = (bcnode, server, stratumsrv)
721 logger.error('Failed to stop server %s\n%s' % (s, traceback.format_exc()))
727 sl.append(s.__class__.__name__)
732 logger.error('Servers taking too long to stop (%s), giving up' % (', '.join(sl)))
737 for fd in s._fd.keys():
741 for i in loggersShare:
742 if hasattr(i, 'stop'):
745 def saveState(SAVE_STATE_FILENAME, t = None):
746 logger = logging.getLogger('saveState')
748 # Then, save data needed to resume work
749 logger.info('Saving work state to \'%s\'...' % (SAVE_STATE_FILENAME,))
753 with open(SAVE_STATE_FILENAME, 'wb') as f:
755 pickle.dump(DupeShareHACK, f)
756 pickle.dump(workLog, f)
761 logger.error('Failed to save work\n' + traceback.format_exc())
763 os.unlink(SAVE_STATE_FILENAME)
765 logger.error(('Failed to unlink \'%s\'; resume may have trouble\n' % (SAVE_STATE_FILENAME,)) + traceback.format_exc())
771 saveState(config.SaveStateFilename, t=t)
772 logging.getLogger('exit').info('Goodbye...')
773 os.kill(os.getpid(), signal.SIGTERM)
780 saveState(config.SaveStateFilename, t=t)
781 logging.getLogger('restart').info('Restarting...')
783 os.execv(sys.argv[0], sys.argv)
785 logging.getLogger('restart').error('Failed to exec\n' + traceback.format_exc())
787 def restoreState(SAVE_STATE_FILENAME):
788 if not os.path.exists(SAVE_STATE_FILENAME):
791 global workLog, DupeShareHACK
793 logger = logging.getLogger('restoreState')
794 s = os.stat(SAVE_STATE_FILENAME)
795 logger.info('Restoring saved state from \'%s\' (%d bytes)' % (SAVE_STATE_FILENAME, s.st_size))
797 with open(SAVE_STATE_FILENAME, 'rb') as f:
801 # Future formats, not supported here
805 # Old format, from 2012-02-02 to 2012-02-03
810 if isinstance(t, dict):
811 # Old format, from 2012-02-03 to 2012-02-03
815 # Current format, from 2012-02-03 onward
816 DupeShareHACK = pickle.load(f)
818 if t + 120 >= time():
819 workLog = pickle.load(f)
821 logger.debug('Skipping restore of expired workLog')
823 logger.error('Failed to restore state\n' + traceback.format_exc())
825 logger.info('State restored successfully')
827 logger.info('Total downtime: %g seconds' % (time() - t,))
830 from jsonrpcserver import JSONRPCListener, JSONRPCServer
831 import interactivemode
832 from networkserver import NetworkListener
835 import authentication
836 from stratumserver import StratumServer
839 if __name__ == "__main__":
840 if not hasattr(config, 'ShareLogging'):
841 config.ShareLogging = ()
842 if hasattr(config, 'DbOptions'):
843 logging.getLogger('backwardCompatibility').warn('DbOptions configuration variable is deprecated; upgrade to ShareLogging var before 2013-03-05')
844 config.ShareLogging = list(config.ShareLogging)
845 config.ShareLogging.append( {
847 'engine': 'postgres',
848 'dbopts': config.DbOptions,
849 'statement': "insert into shares (rem_host, username, our_result, upstream_result, reason, solution) values ({Q(remoteHost)}, {username}, {YN(not(rejectReason))}, {YN(upstreamResult)}, {rejectReason}, decode({solution}, 'hex'))",
851 for i in config.ShareLogging:
852 if not hasattr(i, 'keys'):
854 logging.getLogger('backwardCompatibility').warn('Using short-term backward compatibility for ShareLogging[\'%s\']; be sure to update config before 2012-04-04' % (name,))
855 if name == 'postgres':
858 'engine': 'postgres',
859 'dbopts': parameters,
861 elif name == 'logfile':
863 i['thropts'] = parameters
864 if 'filename' in parameters:
865 i['filename'] = parameters['filename']
866 i['thropts'] = dict(i['thropts'])
867 del i['thropts']['filename']
875 fp, pathname, description = imp.find_module(name, sharelogging.__path__)
876 m = imp.load_module(name, fp, pathname, description)
877 lo = getattr(m, name)(**parameters)
878 loggersShare.append(lo)
880 logging.getLogger('sharelogging').error("Error setting up share logger %s: %s", name, sys.exc_info())
882 if not hasattr(config, 'Authentication'):
883 config.Authentication = ({'module': 'allowall'},)
885 for i in config.Authentication:
889 fp, pathname, description = imp.find_module(name, authentication.__path__)
890 m = imp.load_module(name, fp, pathname, description)
891 lo = getattr(m, name)(**parameters)
892 authenticators.append(lo)
894 logging.getLogger('authentication').error("Error setting up authentication module %s: %s", name, sys.exc_info())
897 if not hasattr(config, 'BitcoinNodeAddresses'):
898 config.BitcoinNodeAddresses = ()
899 for a in config.BitcoinNodeAddresses:
900 LSbc.append(NetworkListener(bcnode, a))
902 if hasattr(config, 'UpstreamBitcoindNode') and config.UpstreamBitcoindNode:
903 BitcoinLink(bcnode, dest=config.UpstreamBitcoindNode)
905 import jsonrpc_getblocktemplate
906 import jsonrpc_getwork
907 import jsonrpc_setworkaux
909 server = JSONRPCServer()
910 server.tls = threading.local()
911 server.tls.wantClear = False
912 if hasattr(config, 'JSONRPCAddress'):
913 logging.getLogger('backwardCompatibility').warn('JSONRPCAddress configuration variable is deprecated; upgrade to JSONRPCAddresses list before 2013-03-05')
914 if not hasattr(config, 'JSONRPCAddresses'):
915 config.JSONRPCAddresses = []
916 config.JSONRPCAddresses.insert(0, config.JSONRPCAddress)
918 for a in config.JSONRPCAddresses:
919 LS.append(JSONRPCListener(server, a))
920 if hasattr(config, 'SecretUser'):
921 server.SecretUser = config.SecretUser
922 server.aux = MM.CoinbaseAux
923 server.getBlockHeader = getBlockHeader
924 server.getBlockTemplate = getBlockTemplate
925 server.receiveShare = receiveShare
926 server.RaiseRedFlags = RaiseRedFlags
927 server.ShareTarget = config.ShareTarget
928 server.checkAuthentication = checkAuthentication
930 if hasattr(config, 'TrustedForwarders'):
931 server.TrustedForwarders = config.TrustedForwarders
932 server.ServerName = config.ServerName
934 stratumsrv = StratumServer()
935 stratumsrv.getStratumJob = getStratumJob
936 stratumsrv.getExistingStratumJob = getExistingStratumJob
937 stratumsrv.receiveShare = receiveShare
938 stratumsrv.getTarget = getTarget
939 stratumsrv.defaultTarget = config.ShareTarget
940 stratumsrv.IsJobValid = IsJobValid
941 stratumsrv.checkAuthentication = checkAuthentication
942 if not hasattr(config, 'StratumAddresses'):
943 config.StratumAddresses = ()
944 for a in config.StratumAddresses:
945 NetworkListener(stratumsrv, a)
949 restoreState(config.SaveStateFilename)
951 prune_thr = threading.Thread(target=WorkLogPruner, args=(workLog,))
952 prune_thr.daemon = True
955 bcnode_thr = threading.Thread(target=bcnode.serve_forever)
956 bcnode_thr.daemon = True
959 stratum_thr = threading.Thread(target=stratumsrv.serve_forever)
960 stratum_thr.daemon = True
963 server.serve_forever()