Debug print pdiff with retargetting
[bitcoin:eloipool.git] / eloipool.py
1 #!/usr/bin/python3
2 # Eloipool - Python Bitcoin pool server
3 # Copyright (C) 2011-2012  Luke Dashjr <luke-jr+eloipool@utopios.org>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Affero General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 import config
19
20 if not hasattr(config, 'ServerName'):
21         config.ServerName = 'Unnamed Eloipool'
22
23 if not hasattr(config, 'ShareTarget'):
24         config.ShareTarget = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
25
26
27 import logging
28
29 if len(logging.root.handlers) == 0:
30         logging.basicConfig(
31                 format='%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s',
32                 level=logging.DEBUG,
33         )
34         for infoOnly in ('checkShare', 'JSONRPCHandler', 'merkleMaker', 'Waker for JSONRPCServer', 'JSONRPCServer'):
35                 logging.getLogger(infoOnly).setLevel(logging.INFO)
36
37 def RaiseRedFlags(reason):
38         logging.getLogger('redflag').critical(reason)
39         return reason
40
41
42 from bitcoin.node import BitcoinLink, BitcoinNode
43 bcnode = BitcoinNode(config.UpstreamNetworkId)
44 bcnode.userAgent += b'Eloipool:0.1/'
45
46 import jsonrpc
47 UpstreamBitcoindJSONRPC = jsonrpc.ServiceProxy(config.UpstreamURI)
48
49
50 from bitcoin.script import BitcoinScript
51 from bitcoin.txn import Txn
52 from base58 import b58decode
53 from struct import pack
54 import subprocess
55 from time import time
56
57 def makeCoinbaseTxn(coinbaseValue, useCoinbaser = True):
58         txn = Txn.new()
59         
60         if useCoinbaser and hasattr(config, 'CoinbaserCmd') and config.CoinbaserCmd:
61                 coinbased = 0
62                 try:
63                         cmd = config.CoinbaserCmd
64                         cmd = cmd.replace('%d', str(coinbaseValue))
65                         p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
66                         nout = int(p.stdout.readline())
67                         for i in range(nout):
68                                 amount = int(p.stdout.readline())
69                                 addr = p.stdout.readline().rstrip(b'\n').decode('utf8')
70                                 pkScript = BitcoinScript.toAddress(addr)
71                                 txn.addOutput(amount, pkScript)
72                                 coinbased += amount
73                 except:
74                         coinbased = coinbaseValue + 1
75                 if coinbased >= coinbaseValue:
76                         logging.getLogger('makeCoinbaseTxn').error('Coinbaser failed!')
77                         txn.outputs = []
78                 else:
79                         coinbaseValue -= coinbased
80         
81         pkScript = BitcoinScript.toAddress(config.TrackerAddr)
82         txn.addOutput(coinbaseValue, pkScript)
83         
84         # TODO
85         # TODO: red flag on dupe coinbase
86         return txn
87
88
89 import jsonrpc_getwork
90 from util import Bits2Target
91
92 workLog = {}
93 userStatus = {}
94 networkTarget = None
95 DupeShareHACK = {}
96
97 server = None
98 def updateBlocks():
99         server.wakeLongpoll()
100
101 def blockChanged():
102         global DupeShareHACK
103         DupeShareHACK = {}
104         jsonrpc_getwork._CheckForDupesHACK = {}
105         global MM, networkTarget, server
106         bits = MM.currentBlock[2]
107         if bits is None:
108                 networkTarget = None
109         else:
110                 networkTarget = Bits2Target(bits)
111         workLog.clear()
112         updateBlocks()
113
114
115 from merklemaker import merkleMaker
116 MM = merkleMaker()
117 MM.__dict__.update(config.__dict__)
118 MM.clearCoinbaseTxn = makeCoinbaseTxn(5000000000, False)  # FIXME
119 MM.clearCoinbaseTxn.assemble()
120 MM.makeCoinbaseTxn = makeCoinbaseTxn
121 MM.onBlockChange = blockChanged
122 MM.onBlockUpdate = updateBlocks
123
124
125 from binascii import b2a_hex
126 from copy import deepcopy
127 from math import log
128 from struct import pack, unpack
129 import threading
130 from time import time
131 from util import RejectedShare, dblsha, hash2int, swap32, target2pdiff
132 import jsonrpc
133 import traceback
134
135 gotwork = None
136 if hasattr(config, 'GotWorkURI'):
137         gotwork = jsonrpc.ServiceProxy(config.GotWorkURI)
138
139 if not hasattr(config, 'DynamicTargetting'):
140         config.DynamicTargetting = 0
141 else:
142         config.DynamicTargetGoal *= 2
143
144 def submitGotwork(info):
145         try:
146                 gotwork.gotwork(info)
147         except:
148                 checkShare.logger.warning('Failed to submit gotwork\n' + traceback.format_exc())
149
150 def getTarget(username, now):
151         if not config.DynamicTargetting:
152                 return None
153         if username in userStatus:
154                 status = userStatus[username]
155         else:
156                 userStatus[username] = [None, now, 0]
157                 return None
158         (targetIn, lastUpdate, work) = status
159         if work <= config.DynamicTargetGoal:
160                 if now < lastUpdate + 120 and (targetIn is None or targetIn >= networkTarget):
161                         return targetIn
162                 if not work:
163                         if targetIn:
164                                 getTarget.logger.debug("No shares from '%s', resetting to minimum target")
165                                 userStatus[username] = [None, now, 0]
166                         return None
167         
168         deltaSec = now - lastUpdate
169         target = targetIn or config.ShareTarget
170         target = int(target * config.DynamicTargetGoal * deltaSec / 120 / work)
171         if target >= config.ShareTarget:
172                 target = None
173         else:
174                 if target < networkTarget:
175                         target = networkTarget
176                 if config.DynamicTargetting == 2:
177                         # Round target to a power of two :)
178                         target = 2**int(log(target, 2) + 1) - 1
179                 if target == config.ShareTarget:
180                         target = None
181         if target != targetIn:
182                 pfx = 'Retargetting %s' % (repr(username),)
183                 tin = targetIn or config.ShareTarget
184                 getTarget.logger.debug("%s from: %064x (pdiff %s)" % (pfx, tin, target2pdiff(tin)))
185                 tgt = target or config.ShareTarget
186                 getTarget.logger.debug("%s   to: %064x (pdiff %s)" % (pfx, tgt, target2pdiff(tgt)))
187         userStatus[username] = [target, now, 0]
188         return target
189 getTarget.logger = logging.getLogger('getTarget')
190
191 def RegisterWork(username, wli, wld):
192         now = time()
193         target = getTarget(username, now)
194         wld = tuple(wld) + (target,)
195         workLog.setdefault(username, {})[wli] = (wld, now)
196         return target or config.ShareTarget
197
198 def getBlockHeader(username):
199         MRD = MM.getMRD()
200         (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk) = MRD[:6]
201         timestamp = pack('<L', int(time()))
202         hdr = b'\2\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'iolE'
203         workLog.setdefault(username, {})[merkleRoot] = (MRD, time())
204         target = RegisterWork(username, merkleRoot, MRD)
205         return (hdr, workLog[username][merkleRoot], target)
206
207 def getBlockTemplate(username):
208         MC = MM.getMC()
209         (dummy, merkleTree, coinbase, prevBlock, bits) = MC[:5]
210         wliPos = coinbase[0] + 2
211         wliLen = coinbase[wliPos - 1]
212         wli = coinbase[wliPos:wliPos+wliLen]
213         target = RegisterWork(username, wli, MC)
214         return (MC, workLog[username][wli], target)
215
216 loggersShare = []
217
218 RBDs = []
219 RBPs = []
220
221 from bitcoin.varlen import varlenEncode, varlenDecode
222 import bitcoin.txn
223 def assembleBlock(blkhdr, txlist):
224         payload = blkhdr
225         payload += varlenEncode(len(txlist))
226         for tx in txlist:
227                 payload += tx.data
228         return payload
229
230 def blockSubmissionThread(payload, blkhash):
231         myblock = (blkhash, payload[4:36])
232         payload = b2a_hex(payload).decode('ascii')
233         nexterr = 0
234         while True:
235                 try:
236                         rv = UpstreamBitcoindJSONRPC.submitblock(payload)
237                         break
238                 except:
239                         try:
240                                 rv = UpstreamBitcoindJSONRPC.getmemorypool(payload)
241                                 if rv is True:
242                                         rv = None
243                                 elif rv is False:
244                                         rv = 'rejected'
245                                 break
246                         except:
247                                 pass
248                         now = time()
249                         if now > nexterr:
250                                 # FIXME: This will show "Method not found" on pre-BIP22 servers
251                                 RaiseRedFlags(traceback.format_exc())
252                                 nexterr = now + 5
253                         if MM.currentBlock[0] not in myblock:
254                                 RaiseRedFlags('Giving up on submitting block upstream')
255                                 return
256         if rv:
257                 # FIXME: The returned value could be a list of multiple responses
258                 RaiseRedFlags('Upstream block submission failed: %s' % (rv,))
259
260 def checkShare(share):
261         shareTime = share['time'] = time()
262         
263         data = share['data']
264         data = data[:80]
265         (prevBlock, height, bits) = MM.currentBlock
266         sharePrevBlock = data[4:36]
267         if sharePrevBlock != prevBlock:
268                 if sharePrevBlock == MM.lastBlock[0]:
269                         raise RejectedShare('stale-prevblk')
270                 raise RejectedShare('bad-prevblk')
271         
272         # TODO: use userid
273         username = share['username']
274         if username not in workLog:
275                 raise RejectedShare('unknown-user')
276         
277         if data[72:76] != bits:
278                 raise RejectedShare('bad-diffbits')
279         
280         # Note that we should accept miners reducing version to 1 if they don't understand 2 yet
281         # FIXME: When the supermajority is upgraded to version 2, stop accepting 1!
282         if data[1:4] != b'\0\0\0' or data[0] > 2:
283                 raise RejectedShare('bad-version')
284         
285         shareMerkleRoot = data[36:68]
286         if 'blkdata' in share:
287                 pl = share['blkdata']
288                 (txncount, pl) = varlenDecode(pl)
289                 cbtxn = bitcoin.txn.Txn(pl)
290                 cbtxn.disassemble(retExtra=True)
291                 coinbase = cbtxn.getCoinbase()
292                 wliPos = coinbase[0] + 2
293                 wliLen = coinbase[wliPos - 1]
294                 wli = coinbase[wliPos:wliPos+wliLen]
295                 mode = 'MC'
296                 moden = 1
297         else:
298                 wli = shareMerkleRoot
299                 mode = 'MRD'
300                 moden = 0
301         
302         MWL = workLog[username]
303         if wli not in MWL:
304                 raise RejectedShare('unknown-work')
305         (wld, issueT) = MWL[wli]
306         share[mode] = wld
307         
308         if data in DupeShareHACK:
309                 raise RejectedShare('duplicate')
310         DupeShareHACK[data] = None
311         
312         blkhash = dblsha(data)
313         if blkhash[28:] != b'\0\0\0\0':
314                 raise RejectedShare('H-not-zero')
315         blkhashn = hash2int(blkhash)
316         
317         global networkTarget
318         logfunc = getattr(checkShare.logger, 'info' if blkhashn <= networkTarget else 'debug')
319         logfunc('BLKHASH: %64x' % (blkhashn,))
320         logfunc(' TARGET: %64x' % (networkTarget,))
321         
322         workMerkleTree = wld[1]
323         workCoinbase = wld[2]
324         workTarget = wld[6]
325         
326         # NOTE: this isn't actually needed for MC mode, but we're abusing it for a trivial share check...
327         txlist = workMerkleTree.data
328         txlist = [deepcopy(txlist[0]),] + txlist[1:]
329         cbtxn = txlist[0]
330         cbtxn.setCoinbase(workCoinbase)
331         cbtxn.assemble()
332         
333         if blkhashn <= networkTarget:
334                 logfunc("Submitting upstream")
335                 RBDs.append( deepcopy( (data, txlist, share.get('blkdata', None), workMerkleTree) ) )
336                 if not moden:
337                         payload = assembleBlock(data, txlist)
338                 else:
339                         payload = share['data'] + share['blkdata']
340                 logfunc('Real block payload: %s' % (b2a_hex(payload).decode('utf8'),))
341                 RBPs.append(payload)
342                 threading.Thread(target=blockSubmissionThread, args=(payload, blkhash)).start()
343                 bcnode.submitBlock(payload)
344                 share['upstreamResult'] = True
345                 MM.updateBlock(blkhash)
346         
347         # Gotwork hack...
348         if gotwork and blkhashn <= config.GotWorkTarget:
349                 try:
350                         coinbaseMrkl = cbtxn.data
351                         coinbaseMrkl += blkhash
352                         steps = workMerkleTree._steps
353                         coinbaseMrkl += pack('B', len(steps))
354                         for step in steps:
355                                 coinbaseMrkl += step
356                         coinbaseMrkl += b"\0\0\0\0"
357                         info = {}
358                         info['hash'] = b2a_hex(blkhash).decode('ascii')
359                         info['header'] = b2a_hex(data).decode('ascii')
360                         info['coinbaseMrkl'] = b2a_hex(coinbaseMrkl).decode('ascii')
361                         thr = threading.Thread(target=submitGotwork, args=(info,))
362                         thr.daemon = True
363                         thr.start()
364                 except:
365                         checkShare.logger.warning('Failed to build gotwork request')
366         
367         if workTarget is None:
368                 workTarget = config.ShareTarget
369         if blkhashn > workTarget:
370                 raise RejectedShare('high-hash')
371         share['target'] = workTarget
372         share['_targethex'] = '%064x' % (workTarget,)
373         
374         shareTimestamp = unpack('<L', data[68:72])[0]
375         if shareTime < issueT - 120:
376                 raise RejectedShare('stale-work')
377         if shareTimestamp < shareTime - 300:
378                 raise RejectedShare('time-too-old')
379         if shareTimestamp > shareTime + 7200:
380                 raise RejectedShare('time-too-new')
381         
382         if config.DynamicTargetting and username in userStatus:
383                 # NOTE: userStatus[username] only doesn't exist across restarts
384                 status = userStatus[username]
385                 target = status[0] or config.ShareTarget
386                 if target == workTarget:
387                         userStatus[username][2] += 1
388                 else:
389                         userStatus[username][2] += float(target) / workTarget
390         
391         if moden:
392                 cbpre = cbtxn.getCoinbase()
393                 cbpreLen = len(cbpre)
394                 if coinbase[:cbpreLen] != cbpre:
395                         raise RejectedShare('bad-cb-prefix')
396                 
397                 # Filter out known "I support" flags, to prevent exploits
398                 for ff in (b'/P2SH/', b'NOP2SH', b'p2sh/CHV', b'p2sh/NOCHV'):
399                         if coinbase.find(ff) > max(-1, cbpreLen - len(ff)):
400                                 raise RejectedShare('bad-cb-flag')
401                 
402                 if len(coinbase) > 100:
403                         raise RejectedShare('bad-cb-length')
404                 
405                 cbtxn.setCoinbase(coinbase)
406                 cbtxn.assemble()
407                 if shareMerkleRoot != workMerkleTree.withFirst(cbtxn):
408                         raise RejectedShare('bad-txnmrklroot')
409                 
410                 allowed = assembleBlock(data, txlist)
411                 if allowed != share['data'] + share['blkdata']:
412                         raise RejectedShare('bad-txns')
413 checkShare.logger = logging.getLogger('checkShare')
414
415 def receiveShare(share):
416         # TODO: username => userid
417         try:
418                 checkShare(share)
419         except RejectedShare as rej:
420                 share['rejectReason'] = str(rej)
421                 raise
422         finally:
423                 if '_origdata' in share:
424                         share['solution'] = share['_origdata']
425                 else:
426                         share['solution'] = b2a_hex(swap32(share['data'])).decode('utf8')
427                 for i in loggersShare:
428                         i(share)
429
430 def newBlockNotification():
431         logging.getLogger('newBlockNotification').info('Received new block notification')
432         MM.updateMerkleTree()
433         # TODO: Force RESPOND TO LONGPOLLS?
434         pass
435
436 def newBlockNotificationSIGNAL(signum, frame):
437         # Use a new thread, in case the signal handler is called with locks held
438         thr = threading.Thread(target=newBlockNotification, name='newBlockNotification via signal %s' % (signum,))
439         thr.daemon = True
440         thr.start()
441
442 from signal import signal, SIGUSR1
443 signal(SIGUSR1, newBlockNotificationSIGNAL)
444
445
446 import os
447 import os.path
448 import pickle
449 import signal
450 import sys
451 from time import sleep
452 import traceback
453
454 SAVE_STATE_FILENAME = 'eloipool.worklog'
455
456 def stopServers():
457         logger = logging.getLogger('stopServers')
458         
459         if hasattr(stopServers, 'already'):
460                 logger.debug('Already tried to stop servers before')
461                 return
462         stopServers.already = True
463         
464         logger.info('Stopping servers...')
465         global bcnode, server
466         servers = (bcnode, server)
467         for s in servers:
468                 s.keepgoing = False
469         for s in servers:
470                 try:
471                         s.wakeup()
472                 except:
473                         logger.error('Failed to stop server %s\n%s' % (s, traceback.format_exc()))
474         i = 0
475         while True:
476                 sl = []
477                 for s in servers:
478                         if s.running:
479                                 sl.append(s.__class__.__name__)
480                 if not sl:
481                         break
482                 i += 1
483                 if i >= 0x100:
484                         logger.error('Servers taking too long to stop (%s), giving up' % (', '.join(sl)))
485                         break
486                 sleep(0.01)
487         
488         for s in servers:
489                 for fd in s._fd.keys():
490                         os.close(fd)
491
492 def saveState(t = None):
493         logger = logging.getLogger('saveState')
494         
495         # Then, save data needed to resume work
496         logger.info('Saving work state to \'%s\'...' % (SAVE_STATE_FILENAME,))
497         i = 0
498         while True:
499                 try:
500                         with open(SAVE_STATE_FILENAME, 'wb') as f:
501                                 pickle.dump(t, f)
502                                 pickle.dump(DupeShareHACK, f)
503                                 pickle.dump(workLog, f)
504                         break
505                 except:
506                         i += 1
507                         if i >= 0x10000:
508                                 logger.error('Failed to save work\n' + traceback.format_exc())
509                                 try:
510                                         os.unlink(SAVE_STATE_FILENAME)
511                                 except:
512                                         logger.error(('Failed to unlink \'%s\'; resume may have trouble\n' % (SAVE_STATE_FILENAME,)) + traceback.format_exc())
513
514 def exit():
515         t = time()
516         stopServers()
517         saveState(t)
518         logging.getLogger('exit').info('Goodbye...')
519         os.kill(os.getpid(), signal.SIGTERM)
520         sys.exit(0)
521
522 def restart():
523         t = time()
524         stopServers()
525         saveState(t)
526         logging.getLogger('restart').info('Restarting...')
527         try:
528                 os.execv(sys.argv[0], sys.argv)
529         except:
530                 logging.getLogger('restart').error('Failed to exec\n' + traceback.format_exc())
531
532 def restoreState():
533         if not os.path.exists(SAVE_STATE_FILENAME):
534                 return
535         
536         global workLog, DupeShareHACK
537         
538         logger = logging.getLogger('restoreState')
539         s = os.stat(SAVE_STATE_FILENAME)
540         logger.info('Restoring saved state from \'%s\' (%d bytes)' % (SAVE_STATE_FILENAME, s.st_size))
541         try:
542                 with open(SAVE_STATE_FILENAME, 'rb') as f:
543                         t = pickle.load(f)
544                         if type(t) == tuple:
545                                 if len(t) > 2:
546                                         # Future formats, not supported here
547                                         ver = t[3]
548                                         # TODO
549                                 
550                                 # Old format, from 2012-02-02 to 2012-02-03
551                                 workLog = t[0]
552                                 DupeShareHACK = t[1]
553                                 t = None
554                         else:
555                                 if isinstance(t, dict):
556                                         # Old format, from 2012-02-03 to 2012-02-03
557                                         DupeShareHACK = t
558                                         t = None
559                                 else:
560                                         # Current format, from 2012-02-03 onward
561                                         DupeShareHACK = pickle.load(f)
562                                 
563                                 if t + 120 >= time():
564                                         workLog = pickle.load(f)
565                                 else:
566                                         logger.debug('Skipping restore of expired workLog')
567         except:
568                 logger.error('Failed to restore state\n' + traceback.format_exc())
569                 return
570         logger.info('State restored successfully')
571         if t:
572                 logger.info('Total downtime: %g seconds' % (time() - t,))
573
574
575 from jsonrpcserver import JSONRPCListener, JSONRPCServer
576 import interactivemode
577 from networkserver import NetworkListener
578 import threading
579 import sharelogging
580 import imp
581
582 if __name__ == "__main__":
583         if not hasattr(config, 'ShareLogging'):
584                 config.ShareLogging = ()
585         if hasattr(config, 'DbOptions'):
586                 logging.getLogger('backwardCompatibility').warn('DbOptions configuration variable is deprecated; upgrade to ShareLogging var before 2013-03-05')
587                 config.ShareLogging = list(config.ShareLogging)
588                 config.ShareLogging.append( {
589                         'type': 'sql',
590                         'engine': 'postgres',
591                         'dbopts': config.DbOptions,
592                         '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'))",
593                 } )
594         for i in config.ShareLogging:
595                 if not hasattr(i, 'keys'):
596                         name, parameters = i
597                         logging.getLogger('backwardCompatibility').warn('Using short-term backward compatibility for ShareLogging[\'%s\']; be sure to update config before 2012-04-04' % (name,))
598                         if name == 'postgres':
599                                 name = 'sql'
600                                 i = {
601                                         'engine': 'postgres',
602                                         'dbopts': parameters,
603                                 }
604                         elif name == 'logfile':
605                                 i = {}
606                                 i['thropts'] = parameters
607                                 if 'filename' in parameters:
608                                         i['filename'] = parameters['filename']
609                                         i['thropts'] = dict(i['thropts'])
610                                         del i['thropts']['filename']
611                         else:
612                                 i = parameters
613                         i['type'] = name
614                 
615                 name = i['type']
616                 parameters = i
617                 try:
618                         fp, pathname, description = imp.find_module(name, sharelogging.__path__)
619                         m = imp.load_module(name, fp, pathname, description)
620                         lo = getattr(m, name)(**parameters)
621                         loggersShare.append(lo.logShare)
622                 except:
623                         logging.getLogger('sharelogging').error("Error setting up share logger %s: %s", name,  sys.exc_info())
624
625         LSbc = []
626         if not hasattr(config, 'BitcoinNodeAddresses'):
627                 config.BitcoinNodeAddresses = ()
628         for a in config.BitcoinNodeAddresses:
629                 LSbc.append(NetworkListener(bcnode, a))
630         
631         if hasattr(config, 'UpstreamBitcoindNode') and config.UpstreamBitcoindNode:
632                 BitcoinLink(bcnode, dest=config.UpstreamBitcoindNode)
633         
634         import jsonrpc_getblocktemplate
635         import jsonrpc_getwork
636         import jsonrpc_setworkaux
637         
638         server = JSONRPCServer()
639         if hasattr(config, 'JSONRPCAddress'):
640                 logging.getLogger('backwardCompatibility').warn('JSONRPCAddress configuration variable is deprecated; upgrade to JSONRPCAddresses list before 2013-03-05')
641                 if not hasattr(config, 'JSONRPCAddresses'):
642                         config.JSONRPCAddresses = []
643                 config.JSONRPCAddresses.insert(0, config.JSONRPCAddress)
644         LS = []
645         for a in config.JSONRPCAddresses:
646                 LS.append(JSONRPCListener(server, a))
647         if hasattr(config, 'SecretUser'):
648                 server.SecretUser = config.SecretUser
649         server.aux = MM.CoinbaseAux
650         server.getBlockHeader = getBlockHeader
651         server.getBlockTemplate = getBlockTemplate
652         server.receiveShare = receiveShare
653         server.RaiseRedFlags = RaiseRedFlags
654         server.ShareTarget = config.ShareTarget
655         
656         if hasattr(config, 'TrustedForwarders'):
657                 server.TrustedForwarders = config.TrustedForwarders
658         server.ServerName = config.ServerName
659         
660         MM.start()
661         
662         restoreState()
663         
664         bcnode_thr = threading.Thread(target=bcnode.serve_forever)
665         bcnode_thr.daemon = True
666         bcnode_thr.start()
667         
668         server.serve_forever()