Bugfix: Correct math to ceil power-of-two pdiff targetting
[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 ceil, log
128 from struct import pack, unpack
129 import threading
130 from time import time
131 from util import RejectedShare, dblsha, hash2int, swap32
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                         truebits = log(target, 2)
179                         target = 2**ceil(truebits) - 1
180                 if target == config.ShareTarget:
181                         target = None
182         if target != targetIn:
183                 pfx = 'Retargetting %s' % (repr(username),)
184                 getTarget.logger.debug("%s from: %064x" % (pfx, targetIn or config.ShareTarget,))
185                 getTarget.logger.debug("%s   to: %064x" % (pfx, target   or config.ShareTarget,))
186         userStatus[username] = [target, now, 0]
187         return target
188 getTarget.logger = logging.getLogger('getTarget')
189
190 def RegisterWork(username, wli, wld):
191         now = time()
192         target = getTarget(username, now)
193         wld = tuple(wld) + (target,)
194         workLog.setdefault(username, {})[wli] = (wld, now)
195         return target or config.ShareTarget
196
197 def getBlockHeader(username):
198         MRD = MM.getMRD()
199         (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk) = MRD[:6]
200         timestamp = pack('<L', int(time()))
201         hdr = b'\2\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'iolE'
202         workLog.setdefault(username, {})[merkleRoot] = (MRD, time())
203         target = RegisterWork(username, merkleRoot, MRD)
204         return (hdr, workLog[username][merkleRoot], target)
205
206 def getBlockTemplate(username):
207         MC = MM.getMC()
208         (dummy, merkleTree, coinbase, prevBlock, bits) = MC[:5]
209         wliPos = coinbase[0] + 2
210         wliLen = coinbase[wliPos - 1]
211         wli = coinbase[wliPos:wliPos+wliLen]
212         target = RegisterWork(username, wli, MC)
213         return (MC, workLog[username][wli], target)
214
215 loggersShare = []
216
217 RBDs = []
218 RBPs = []
219
220 from bitcoin.varlen import varlenEncode, varlenDecode
221 import bitcoin.txn
222 def assembleBlock(blkhdr, txlist):
223         payload = blkhdr
224         payload += varlenEncode(len(txlist))
225         for tx in txlist:
226                 payload += tx.data
227         return payload
228
229 def blockSubmissionThread(payload, blkhash):
230         myblock = (blkhash, payload[4:36])
231         payload = b2a_hex(payload).decode('ascii')
232         nexterr = 0
233         while True:
234                 try:
235                         rv = UpstreamBitcoindJSONRPC.submitblock(payload)
236                         break
237                 except:
238                         try:
239                                 rv = UpstreamBitcoindJSONRPC.getmemorypool(payload)
240                                 if rv is True:
241                                         rv = None
242                                 elif rv is False:
243                                         rv = 'rejected'
244                                 break
245                         except:
246                                 pass
247                         now = time()
248                         if now > nexterr:
249                                 # FIXME: This will show "Method not found" on pre-BIP22 servers
250                                 RaiseRedFlags(traceback.format_exc())
251                                 nexterr = now + 5
252                         if MM.currentBlock[0] not in myblock:
253                                 RaiseRedFlags('Giving up on submitting block upstream')
254                                 return
255         if rv:
256                 # FIXME: The returned value could be a list of multiple responses
257                 RaiseRedFlags('Upstream block submission failed: %s' % (rv,))
258
259 def checkShare(share):
260         shareTime = share['time'] = time()
261         
262         data = share['data']
263         data = data[:80]
264         (prevBlock, height, bits) = MM.currentBlock
265         sharePrevBlock = data[4:36]
266         if sharePrevBlock != prevBlock:
267                 if sharePrevBlock == MM.lastBlock[0]:
268                         raise RejectedShare('stale-prevblk')
269                 raise RejectedShare('bad-prevblk')
270         
271         # TODO: use userid
272         username = share['username']
273         if username not in workLog:
274                 raise RejectedShare('unknown-user')
275         
276         if data[72:76] != bits:
277                 raise RejectedShare('bad-diffbits')
278         
279         # Note that we should accept miners reducing version to 1 if they don't understand 2 yet
280         # FIXME: When the supermajority is upgraded to version 2, stop accepting 1!
281         if data[1:4] != b'\0\0\0' or data[0] > 2:
282                 raise RejectedShare('bad-version')
283         
284         shareMerkleRoot = data[36:68]
285         if 'blkdata' in share:
286                 pl = share['blkdata']
287                 (txncount, pl) = varlenDecode(pl)
288                 cbtxn = bitcoin.txn.Txn(pl)
289                 cbtxn.disassemble(retExtra=True)
290                 coinbase = cbtxn.getCoinbase()
291                 wliPos = coinbase[0] + 2
292                 wliLen = coinbase[wliPos - 1]
293                 wli = coinbase[wliPos:wliPos+wliLen]
294                 mode = 'MC'
295                 moden = 1
296         else:
297                 wli = shareMerkleRoot
298                 mode = 'MRD'
299                 moden = 0
300         
301         MWL = workLog[username]
302         if wli not in MWL:
303                 raise RejectedShare('unknown-work')
304         (wld, issueT) = MWL[wli]
305         share[mode] = wld
306         
307         if data in DupeShareHACK:
308                 raise RejectedShare('duplicate')
309         DupeShareHACK[data] = None
310         
311         blkhash = dblsha(data)
312         if blkhash[28:] != b'\0\0\0\0':
313                 raise RejectedShare('H-not-zero')
314         blkhashn = hash2int(blkhash)
315         
316         global networkTarget
317         logfunc = getattr(checkShare.logger, 'info' if blkhashn <= networkTarget else 'debug')
318         logfunc('BLKHASH: %64x' % (blkhashn,))
319         logfunc(' TARGET: %64x' % (networkTarget,))
320         
321         workMerkleTree = wld[1]
322         workCoinbase = wld[2]
323         workTarget = wld[6]
324         
325         # NOTE: this isn't actually needed for MC mode, but we're abusing it for a trivial share check...
326         txlist = workMerkleTree.data
327         txlist = [deepcopy(txlist[0]),] + txlist[1:]
328         cbtxn = txlist[0]
329         cbtxn.setCoinbase(workCoinbase)
330         cbtxn.assemble()
331         
332         if blkhashn <= networkTarget:
333                 logfunc("Submitting upstream")
334                 RBDs.append( deepcopy( (data, txlist, share.get('blkdata', None), workMerkleTree) ) )
335                 if not moden:
336                         payload = assembleBlock(data, txlist)
337                 else:
338                         payload = share['data'] + share['blkdata']
339                 logfunc('Real block payload: %s' % (b2a_hex(payload).decode('utf8'),))
340                 RBPs.append(payload)
341                 threading.Thread(target=blockSubmissionThread, args=(payload, blkhash)).start()
342                 bcnode.submitBlock(payload)
343                 share['upstreamResult'] = True
344                 MM.updateBlock(blkhash)
345         
346         # Gotwork hack...
347         if gotwork and blkhashn <= config.GotWorkTarget:
348                 try:
349                         coinbaseMrkl = cbtxn.data
350                         coinbaseMrkl += blkhash
351                         steps = workMerkleTree._steps
352                         coinbaseMrkl += pack('B', len(steps))
353                         for step in steps:
354                                 coinbaseMrkl += step
355                         coinbaseMrkl += b"\0\0\0\0"
356                         info = {}
357                         info['hash'] = b2a_hex(blkhash).decode('ascii')
358                         info['header'] = b2a_hex(data).decode('ascii')
359                         info['coinbaseMrkl'] = b2a_hex(coinbaseMrkl).decode('ascii')
360                         thr = threading.Thread(target=submitGotwork, args=(info,))
361                         thr.daemon = True
362                         thr.start()
363                 except:
364                         checkShare.logger.warning('Failed to build gotwork request')
365         
366         if workTarget is None:
367                 workTarget = config.ShareTarget
368         if blkhashn > workTarget:
369                 raise RejectedShare('high-hash')
370         share['target'] = workTarget
371         share['_targethex'] = '%064x' % (workTarget,)
372         
373         shareTimestamp = unpack('<L', data[68:72])[0]
374         if shareTime < issueT - 120:
375                 raise RejectedShare('stale-work')
376         if shareTimestamp < shareTime - 300:
377                 raise RejectedShare('time-too-old')
378         if shareTimestamp > shareTime + 7200:
379                 raise RejectedShare('time-too-new')
380         
381         if config.DynamicTargetting and username in userStatus:
382                 # NOTE: userStatus[username] only doesn't exist across restarts
383                 status = userStatus[username]
384                 target = status[0] or config.ShareTarget
385                 if target == workTarget:
386                         userStatus[username][2] += 1
387                 else:
388                         userStatus[username][2] += float(target) / workTarget
389         
390         if moden:
391                 cbpre = cbtxn.getCoinbase()
392                 cbpreLen = len(cbpre)
393                 if coinbase[:cbpreLen] != cbpre:
394                         raise RejectedShare('bad-cb-prefix')
395                 
396                 # Filter out known "I support" flags, to prevent exploits
397                 for ff in (b'/P2SH/', b'NOP2SH', b'p2sh/CHV', b'p2sh/NOCHV'):
398                         if coinbase.find(ff) > max(-1, cbpreLen - len(ff)):
399                                 raise RejectedShare('bad-cb-flag')
400                 
401                 if len(coinbase) > 100:
402                         raise RejectedShare('bad-cb-length')
403                 
404                 cbtxn.setCoinbase(coinbase)
405                 cbtxn.assemble()
406                 if shareMerkleRoot != workMerkleTree.withFirst(cbtxn):
407                         raise RejectedShare('bad-txnmrklroot')
408                 
409                 allowed = assembleBlock(data, txlist)
410                 if allowed != share['data'] + share['blkdata']:
411                         raise RejectedShare('bad-txns')
412 checkShare.logger = logging.getLogger('checkShare')
413
414 def receiveShare(share):
415         # TODO: username => userid
416         try:
417                 checkShare(share)
418         except RejectedShare as rej:
419                 share['rejectReason'] = str(rej)
420                 raise
421         finally:
422                 if '_origdata' in share:
423                         share['solution'] = share['_origdata']
424                 else:
425                         share['solution'] = b2a_hex(swap32(share['data'])).decode('utf8')
426                 for i in loggersShare:
427                         i(share)
428
429 def newBlockNotification():
430         logging.getLogger('newBlockNotification').info('Received new block notification')
431         MM.updateMerkleTree()
432         # TODO: Force RESPOND TO LONGPOLLS?
433         pass
434
435 def newBlockNotificationSIGNAL(signum, frame):
436         # Use a new thread, in case the signal handler is called with locks held
437         thr = threading.Thread(target=newBlockNotification, name='newBlockNotification via signal %s' % (signum,))
438         thr.daemon = True
439         thr.start()
440
441 from signal import signal, SIGUSR1
442 signal(SIGUSR1, newBlockNotificationSIGNAL)
443
444
445 import os
446 import os.path
447 import pickle
448 import signal
449 import sys
450 from time import sleep
451 import traceback
452
453 SAVE_STATE_FILENAME = 'eloipool.worklog'
454
455 def stopServers():
456         logger = logging.getLogger('stopServers')
457         
458         if hasattr(stopServers, 'already'):
459                 logger.debug('Already tried to stop servers before')
460                 return
461         stopServers.already = True
462         
463         logger.info('Stopping servers...')
464         global bcnode, server
465         servers = (bcnode, server)
466         for s in servers:
467                 s.keepgoing = False
468         for s in servers:
469                 try:
470                         s.wakeup()
471                 except:
472                         logger.error('Failed to stop server %s\n%s' % (s, traceback.format_exc()))
473         i = 0
474         while True:
475                 sl = []
476                 for s in servers:
477                         if s.running:
478                                 sl.append(s.__class__.__name__)
479                 if not sl:
480                         break
481                 i += 1
482                 if i >= 0x100:
483                         logger.error('Servers taking too long to stop (%s), giving up' % (', '.join(sl)))
484                         break
485                 sleep(0.01)
486         
487         for s in servers:
488                 for fd in s._fd.keys():
489                         os.close(fd)
490
491 def saveState(t = None):
492         logger = logging.getLogger('saveState')
493         
494         # Then, save data needed to resume work
495         logger.info('Saving work state to \'%s\'...' % (SAVE_STATE_FILENAME,))
496         i = 0
497         while True:
498                 try:
499                         with open(SAVE_STATE_FILENAME, 'wb') as f:
500                                 pickle.dump(t, f)
501                                 pickle.dump(DupeShareHACK, f)
502                                 pickle.dump(workLog, f)
503                         break
504                 except:
505                         i += 1
506                         if i >= 0x10000:
507                                 logger.error('Failed to save work\n' + traceback.format_exc())
508                                 try:
509                                         os.unlink(SAVE_STATE_FILENAME)
510                                 except:
511                                         logger.error(('Failed to unlink \'%s\'; resume may have trouble\n' % (SAVE_STATE_FILENAME,)) + traceback.format_exc())
512
513 def exit():
514         t = time()
515         stopServers()
516         saveState(t)
517         logging.getLogger('exit').info('Goodbye...')
518         os.kill(os.getpid(), signal.SIGTERM)
519         sys.exit(0)
520
521 def restart():
522         t = time()
523         stopServers()
524         saveState(t)
525         logging.getLogger('restart').info('Restarting...')
526         try:
527                 os.execv(sys.argv[0], sys.argv)
528         except:
529                 logging.getLogger('restart').error('Failed to exec\n' + traceback.format_exc())
530
531 def restoreState():
532         if not os.path.exists(SAVE_STATE_FILENAME):
533                 return
534         
535         global workLog, DupeShareHACK
536         
537         logger = logging.getLogger('restoreState')
538         s = os.stat(SAVE_STATE_FILENAME)
539         logger.info('Restoring saved state from \'%s\' (%d bytes)' % (SAVE_STATE_FILENAME, s.st_size))
540         try:
541                 with open(SAVE_STATE_FILENAME, 'rb') as f:
542                         t = pickle.load(f)
543                         if type(t) == tuple:
544                                 if len(t) > 2:
545                                         # Future formats, not supported here
546                                         ver = t[3]
547                                         # TODO
548                                 
549                                 # Old format, from 2012-02-02 to 2012-02-03
550                                 workLog = t[0]
551                                 DupeShareHACK = t[1]
552                                 t = None
553                         else:
554                                 if isinstance(t, dict):
555                                         # Old format, from 2012-02-03 to 2012-02-03
556                                         DupeShareHACK = t
557                                         t = None
558                                 else:
559                                         # Current format, from 2012-02-03 onward
560                                         DupeShareHACK = pickle.load(f)
561                                 
562                                 if t + 120 >= time():
563                                         workLog = pickle.load(f)
564                                 else:
565                                         logger.debug('Skipping restore of expired workLog')
566         except:
567                 logger.error('Failed to restore state\n' + traceback.format_exc())
568                 return
569         logger.info('State restored successfully')
570         if t:
571                 logger.info('Total downtime: %g seconds' % (time() - t,))
572
573
574 from jsonrpcserver import JSONRPCListener, JSONRPCServer
575 import interactivemode
576 from networkserver import NetworkListener
577 import threading
578 import sharelogging
579 import imp
580
581 if __name__ == "__main__":
582         if not hasattr(config, 'ShareLogging'):
583                 config.ShareLogging = ()
584         if hasattr(config, 'DbOptions'):
585                 logging.getLogger('backwardCompatibility').warn('DbOptions configuration variable is deprecated; upgrade to ShareLogging var before 2013-03-05')
586                 config.ShareLogging = list(config.ShareLogging)
587                 config.ShareLogging.append( {
588                         'type': 'sql',
589                         'engine': 'postgres',
590                         'dbopts': config.DbOptions,
591                         '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'))",
592                 } )
593         for i in config.ShareLogging:
594                 if not hasattr(i, 'keys'):
595                         name, parameters = i
596                         logging.getLogger('backwardCompatibility').warn('Using short-term backward compatibility for ShareLogging[\'%s\']; be sure to update config before 2012-04-04' % (name,))
597                         if name == 'postgres':
598                                 name = 'sql'
599                                 i = {
600                                         'engine': 'postgres',
601                                         'dbopts': parameters,
602                                 }
603                         elif name == 'logfile':
604                                 i = {}
605                                 i['thropts'] = parameters
606                                 if 'filename' in parameters:
607                                         i['filename'] = parameters['filename']
608                                         i['thropts'] = dict(i['thropts'])
609                                         del i['thropts']['filename']
610                         else:
611                                 i = parameters
612                         i['type'] = name
613                 
614                 name = i['type']
615                 parameters = i
616                 try:
617                         fp, pathname, description = imp.find_module(name, sharelogging.__path__)
618                         m = imp.load_module(name, fp, pathname, description)
619                         lo = getattr(m, name)(**parameters)
620                         loggersShare.append(lo.logShare)
621                 except:
622                         logging.getLogger('sharelogging').error("Error setting up share logger %s: %s", name,  sys.exc_info())
623
624         LSbc = []
625         if not hasattr(config, 'BitcoinNodeAddresses'):
626                 config.BitcoinNodeAddresses = ()
627         for a in config.BitcoinNodeAddresses:
628                 LSbc.append(NetworkListener(bcnode, a))
629         
630         if hasattr(config, 'UpstreamBitcoindNode') and config.UpstreamBitcoindNode:
631                 BitcoinLink(bcnode, dest=config.UpstreamBitcoindNode)
632         
633         import jsonrpc_getblocktemplate
634         import jsonrpc_getwork
635         import jsonrpc_setworkaux
636         
637         server = JSONRPCServer()
638         if hasattr(config, 'JSONRPCAddress'):
639                 logging.getLogger('backwardCompatibility').warn('JSONRPCAddress configuration variable is deprecated; upgrade to JSONRPCAddresses list before 2013-03-05')
640                 if not hasattr(config, 'JSONRPCAddresses'):
641                         config.JSONRPCAddresses = []
642                 config.JSONRPCAddresses.insert(0, config.JSONRPCAddress)
643         LS = []
644         for a in config.JSONRPCAddresses:
645                 LS.append(JSONRPCListener(server, a))
646         if hasattr(config, 'SecretUser'):
647                 server.SecretUser = config.SecretUser
648         server.aux = MM.CoinbaseAux
649         server.getBlockHeader = getBlockHeader
650         server.getBlockTemplate = getBlockTemplate
651         server.receiveShare = receiveShare
652         server.RaiseRedFlags = RaiseRedFlags
653         server.ShareTarget = config.ShareTarget
654         
655         if hasattr(config, 'TrustedForwarders'):
656                 server.TrustedForwarders = config.TrustedForwarders
657         server.ServerName = config.ServerName
658         
659         MM.start()
660         
661         restoreState()
662         
663         bcnode_thr = threading.Thread(target=bcnode.serve_forever)
664         bcnode_thr.daemon = True
665         bcnode_thr.start()
666         
667         server.serve_forever()