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