1 # Eloipool - Python Bitcoin pool server
2 # Copyright (C) 2011-2012 Luke Dashjr <luke-jr+eloipool@utopios.org>
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 from binascii import b2a_hex
19 from bitcoin.script import countSigOps
20 from bitcoin.txn import Txn
21 from bitcoin.varlen import varlenEncode, varlenDecode
22 from collections import deque
23 from copy import deepcopy
24 from queue import Queue
28 from merkletree import MerkleTree
30 from struct import pack
32 from time import sleep, time
35 _makeCoinbase = [0, 0]
38 def MakeBlockHeader(MRD):
39 (merkleRoot, merkleTree, coinbase, prevBlock, bits) = MRD[:5]
40 timestamp = pack('<L', int(time()))
41 hdr = b'\2\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'iolE'
44 def assembleBlock(blkhdr, txlist):
46 payload += varlenEncode(len(txlist))
51 class merkleMaker(threading.Thread):
58 'transactions/remove',
62 'capabilities': GBTCaps,
65 'capabilities': GBTCaps,
69 def __init__(self, *a, **k):
70 super().__init__(*a, **k)
72 self.logger = logging.getLogger('merkleMaker')
73 self.CoinbasePrefix = b''
75 self.isOverflowed = False
77 self.MinimumTxnUpdateWait = 5
79 self.DifficultyChangeMod = 2016
80 self.MinimumTemplateAcceptanceRatio = 0
81 self.MinimumTemplateScore = 1
82 self.currentBlock = (None, None, None)
83 self.lastBlock = (None, None, None)
86 self.TemplateSources = list(getattr(self, 'TemplateSources', ()))
87 self.TemplateChecks = list(getattr(self, 'TemplateChecks', ()))
88 if getattr(self, 'BlockSubmissions', None) is None:
89 self.BlockSubmissions = ()
90 if hasattr(self, 'UpstreamURI'):
91 self.TemplateSources.append({
92 'name': 'UpstreamURI',
93 'uri': self.UpstreamURI,
97 for a in (self.TemplateSources + self.TemplateChecks + list(self.BlockSubmissions)):
98 if not ('name' in a and 'uri' in a):
100 URI2Name.setdefault(a['uri'], a['name'])
101 Name2URI.setdefault(a['name'], a['uri'])
102 def URINamePair(a, defname):
104 a['name'] = URI2Name.get(a['uri'], defname)
106 a['uri'] = Name2URI[a['name']]
109 if uri not in _URI2Access:
110 access = jsonrpc.ServiceProxy(uri)
111 access.OldGMP = False
112 _URI2Access[uri] = access
113 return _URI2Access[uri]
115 for i in range(len(self.TemplateSources)):
116 TS = self.TemplateSources[i]
117 URINamePair(TS, 'TemplateSources[%u]' % (i,))
118 TS.setdefault('priority', 0)
119 TS.setdefault('weight', 1)
120 TS['access'] = URI2Access(TS['uri'])
121 LeveledTS.setdefault(TS['priority'], []).append(TS)
122 LeveledTS = tuple(x[1] for x in sorted(LeveledTS.items()))
123 self.TemplateSources = LeveledTS
124 for i in range(len(self.TemplateChecks)):
125 TC = self.TemplateChecks[i]
126 URINamePair(TC, 'TemplateChecks[%u]' % (i,))
127 TC.setdefault('unanimous', False)
128 TC.setdefault('weight', 1)
129 TC['access'] = URI2Access(TC['uri'])
130 for i in range(len(getattr(self, 'BlockSubmissions', ()))):
131 BS = self.BlockSubmissions[i]
132 URINamePair(BS, 'BlockSubmissions[%u]' % (i,))
133 BS['access'] = URI2Access(BS['uri'])
136 self.readyCV = threading.Condition()
138 self.currentMerkleTree = None
139 self.merkleRoots = deque(maxlen=self.WorkQueueSizeRegular[1])
140 self.LowestMerkleRoots = self.WorkQueueSizeRegular[1]
142 if not hasattr(self, 'WorkQueueSizeClear'):
143 self.WorkQueueSizeClear = self.WorkQueueSizeLongpoll
144 self._MaxClearSize = max(self.WorkQueueSizeClear[1], self.WorkQueueSizeLongpoll[1])
145 self.clearMerkleRoots = Queue(self._MaxClearSize)
146 self.LowestClearMerkleRoots = self.WorkQueueSizeClear[1]
147 self.nextMerkleRoots = Queue(self._MaxClearSize)
149 if not hasattr(self, 'WarningDelay'):
150 self.WarningDelay = max(15, self.MinimumTxnUpdateWait * 2)
151 if not hasattr(self, 'WarningDelayTxnLongpoll'):
152 self.WarningDelayTxnLongpoll = self.WarningDelay
153 if not hasattr(self, 'WarningDelayMerkleUpdate'):
154 self.WarningDelayMerkleUpdate = self.WarningDelay
156 self.lastMerkleUpdate = 0
157 self.nextMerkleUpdate = 0
159 def createClearMerkleTree(self, height):
160 subsidy = 5000000000 >> (height // 210000)
161 cbtxn = self.makeCoinbaseTxn(subsidy, False)
163 return MerkleTree([cbtxn])
165 def updateBlock(self, newBlock, height = None, bits = None, _HBH = None):
166 if newBlock == self.currentBlock[0]:
167 if height in (None, self.currentBlock[1]) and bits in (None, self.currentBlock[2]):
169 if not self.currentBlock[2] is None:
170 self.logger.error('Was working on block with wrong specs: %s (height: %d->%d; bits: %s->%s' % (
171 b2a_hex(newBlock[::-1]).decode('utf8'),
172 self.currentBlock[1],
174 b2a_hex(self.currentBlock[2][::-1]).decode('utf8'),
175 b2a_hex(bits[::-1]).decode('utf8'),
177 if self.needMerkle == 1:
178 self.needMerkle = False
181 # Old block is invalid
182 if self.currentBlock[0] != newBlock:
183 self.lastBlock = self.currentBlock
185 lastHeight = self.currentBlock[1]
187 height = self.currentBlock[1] + 1
189 if height % self.DifficultyChangeMod == 1 or self.currentBlock[2] is None:
190 self.logger.warning('New block: %s (height %d; bits: UNKNOWN)' % (b2a_hex(newBlock[::-1]).decode('utf8'), height))
191 # Pretend to be 1 lower height, so we possibly retain nextMerkleRoots
192 self.currentBlock = (None, height - 1, None)
193 OCMR = self.clearMerkleRoots
194 self.clearMerkleRoots = Queue(self.WorkQueueSizeClear[1])
197 self.merkleRoots.clear()
201 bits = self.currentBlock[2]
204 _HBH = (b2a_hex(newBlock[::-1]).decode('utf8'), b2a_hex(bits[::-1]).decode('utf8'))
205 self.logger.info('New block: %s (height: %d; bits: %s)' % (_HBH[0], height, _HBH[1]))
206 self.currentBlock = (newBlock, height, bits)
208 if lastHeight != height:
209 # TODO: Perhaps reuse clear merkle trees more intelligently
210 OCMR = self.clearMerkleRoots
211 if lastHeight == height - 1:
212 self.curClearMerkleTree = self.nextMerkleTree
213 self.clearMerkleRoots = self.nextMerkleRoots
214 self.logger.debug('Adopting next-height clear merkleroots :)')
217 self.logger.warning('Change from height %d->%d; no longpoll merkleroots available!' % (lastHeight, height))
218 self.curClearMerkleTree = self.createClearMerkleTree(height)
219 self.clearMerkleRoots = Queue(self.WorkQueueSizeClear[1])
222 self.nextMerkleTree = self.createClearMerkleTree(height + 1)
223 self.nextMerkleRoots = Queue(self._MaxClearSize)
225 self.logger.debug('Already using clear merkleroots for this height')
226 self.currentMerkleTree = self.curClearMerkleTree
227 self.merkleRoots.clear()
232 self.readyCV.notify_all()
237 def _trimBlock(self, MP, txnlist, txninfo, floodn, msgf):
238 fee = txninfo[-1].get('fee', None)
240 raise self._floodCritical(now, floodn, doin=msgf('fees unknown'))
242 # FIXME: coinbasevalue is *not* guaranteed to exist here
243 MP['coinbasevalue'] -= fee
250 # Aggressive "Power Of Two": Remove transactions even with fees to reach our goal
251 def _APOT(self, txninfopot, MP, POTInfo):
254 for txn in txninfopot:
255 if txn.get('fee') is None:
256 self._floodWarning(now, 'APOT-No-Fees', doin='Upstream didn\'t provide fee information required for aggressive POT', logf=self.logger.info)
260 feesTrimmed += txn['fee']
262 MP['coinbasevalue'] -= feesTrimmed
264 POTInfo[2] = [feeTxnsTrimmed, feesTrimmed]
265 self._floodWarning(now, 'POT-Trimming-Fees', doin='Aggressive POT trimming %d transactions with %d.%08d BTC total fees' % (feeTxnsTrimmed, feesTrimmed//100000000, feesTrimmed % 100000000), logf=self.logger.debug)
269 def _makeBlockSafe(self, MP, txnlist, txninfo):
270 blocksize = sum(map(len, txnlist)) + 80
271 while blocksize > 934464: # 1 "MB" limit - 64 KB breathing room
272 txnsize = len(txnlist[-1])
273 self._trimBlock(MP, txnlist, txninfo, 'SizeLimit', lambda x: 'Making blocks over 1 MB size limit (%d bytes; %s)' % (blocksize, x))
276 # NOTE: This check doesn't work at all without BIP22 transaction obj format
277 blocksigops = sum(a.get('sigops', 0) for a in txninfo)
278 while blocksigops > 19488: # 20k limit - 0x200 breathing room
279 txnsigops = txninfo[-1]['sigops']
280 self._trimBlock(MP, txnlist, txninfo, 'SigOpLimit', lambda x: 'Making blocks over 20k SigOp limit (%d; %s)' % (blocksigops, x))
281 blocksigops -= txnsigops
283 # Aim to produce blocks with "Power Of Two" transaction counts
284 # This helps avoid any chance of someone abusing CVE-2012-2459 with them
285 POTMode = getattr(self, 'POT', 1)
286 txncount = len(txnlist) + 1
288 feetxncount = txncount
289 for i in range(txncount - 2, -1, -1):
290 if 'fee' not in txninfo[i] or txninfo[i]['fee']:
294 if getattr(self, 'Greedy', None):
295 # Aim to cut off extra zero-fee transactions on the end
296 # NOTE: not cutting out ones intermixed, in case of dependencies
297 idealtxncount = feetxncount
299 idealtxncount = txncount
301 pot = 2**int(log(idealtxncount, 2))
302 POTInfo = MP['POTInfo'] = [[idealtxncount, feetxncount, txncount], [pot, None], None]
303 if pot < idealtxncount:
304 if pot * 2 <= txncount:
306 elif pot >= feetxncount:
308 elif POTMode > 1 and self._APOT(txninfo[pot-1:], MP, POTInfo):
309 # Trimmed even transactions with fees
313 self._floodWarning(now, 'Non-POT', doin='Making merkle tree with %d transactions (ideal: %d; max: %d)' % (pot, idealtxncount, txncount))
319 def _CallGBT(self, TS):
320 access = TS['access']
321 self.logger.debug('Requesting new template from \'%s\'' % (TS['name'],))
323 # First, try BIP 22 standard getblocktemplate :)
324 MP = access.getblocktemplate(self.GBTReq)
325 access.OldGMP = False
328 # Failing that, give BIP 22 draft (2012-02 through 2012-07) getmemorypool a chance
329 MP = access.getmemorypool(self.GMPReq)
332 # Finally, fall back to bitcoind 0.5/0.6 getmemorypool
333 MP = access.getmemorypool()
337 # This way, we get the error from the BIP22 call if the old one fails too
340 # Pre-BIP22 server (bitcoind <0.7 or Eloipool <20120513)
341 if not access.OldGMP:
343 self.logger.warning('Upstream \'%s\' is not BIP 22 compatible' % (TS['name'],))
347 def _ProcessGBT(self, MP, TS = None):
351 prevBlock = bytes.fromhex(MP['previousblockhash'])[::-1]
352 if 'height' not in MP:
353 MP['height'] = TS['access'].getinfo()['blocks'] + 1
354 height = MP['height']
355 bits = bytes.fromhex(MP['bits'])[::-1]
356 (MP['_bits'], MP['_prevBlock']) = (bits, prevBlock)
357 if (prevBlock, height, bits) != self.currentBlock and (self.currentBlock[1] is None or height > self.currentBlock[1]):
358 self.updateBlock(prevBlock, height, bits, _HBH=(MP['previousblockhash'], MP['bits']))
360 txnlist = MP['transactions']
361 if len(txnlist) and isinstance(txnlist[0], dict):
363 txnlist = tuple(a['data'] for a in txnlist)
364 elif 'transactionfees' in MP:
365 # Backward compatibility with pre-BIP22 gmp_fees branch
366 txninfo = [{'fee':a} for a in MP['transactionfees']]
368 # Backward compatibility with pre-BIP22 hex-only (bitcoind <0.7, Eloipool <future)
369 txninfo = [{}] * len(txnlist)
370 # TODO: cache Txn or at least txid from previous merkle roots?
371 txnlist = [a for a in map(bytes.fromhex, txnlist)]
373 self._makeBlockSafe(MP, txnlist, txninfo)
375 cbtxn = self.makeCoinbaseTxn(MP['coinbasevalue'])
376 cbtxn.setCoinbase(b'\0\0')
378 txnlist.insert(0, cbtxn.data)
382 txnlist = [a for a in map(Txn, txnlist[1:])]
383 txnlist.insert(0, cbtxn)
384 txnlist = list(txnlist)
385 newMerkleTree = MerkleTree(txnlist)
386 newMerkleTree.POTInfo = MP.get('POTInfo')
387 newMerkleTree.MP = MP
388 newMerkleTree.oMP = oMP
392 def _CheckTemplate(self, newMerkleTree, TS):
393 TCList = self.TemplateChecks
395 if 'proposal' not in newMerkleTree.oMP.get('capabilities', ()):
400 'access': TS['access'],
406 MP = newMerkleTree.MP
407 (prevBlock, height, bits) = (MP['_prevBlock'], MP['height'], MP['_bits'])
408 txnlist = newMerkleTree.data
411 coinbase = self.makeCoinbase(height=height)
412 cbtxn.setCoinbase(coinbase)
414 merkleRoot = newMerkleTree.merkleRoot()
415 MRD = (merkleRoot, newMerkleTree, coinbase, prevBlock, bits)
416 blkhdr = MakeBlockHeader(MRD)
417 data = assembleBlock(blkhdr, txnlist)
420 "data": b2a_hex(data).decode('utf8'),
428 caccess = TC['access']
430 propose = caccess.getblocktemplate(ProposeReq)
431 except (socket.error, ValueError) as e:
432 self.logger.error('Upstream \'%s\' errored on proposal from \'%s\': %s' % (TC['name'], TS['name'], e))
433 ProposalErrors[TC['name']] = e
436 AcceptedScore += TC['weight']
437 self.logger.debug('Upstream \'%s\' accepted proposal' % (TC['name'],))
438 elif propose == 'orphan':
439 self.logger.debug('Upstream \'%s\' considered proposal an orphan' % (TC['name'],))
440 ProposalErrors[TC['name']] = propose
442 RejectedScore += TC['weight']
443 Rejections[TC['name']] = propose
445 propose = propose['reject-reason']
448 self.logger.error('Upstream \'%s\' rejected proposed block from \'%s\': %s' % (TC['name'], TS['name'], propose))
452 'merkleTree': newMerkleTree,
453 'AcceptedScore': AcceptedScore,
454 'RejectedScore': RejectedScore,
455 'Rejections': Rejections,
456 'ProposalErrors': ProposalErrors,
458 self.RejectedProposal = RPInfo
464 with open('RejectedProposals/%d_%d' % (int(time()), _filecounter), 'wb') as f:
465 pickle.dump(RPInfo, f)
469 TotalScore = AcceptedScore + RejectedScore
471 return (AcceptedScore, TotalScore)
473 def _updateMerkleTree_fromTS(self, TS):
474 MP = self._CallGBT(TS)
475 newMerkleTree = self._ProcessGBT(MP, TS)
477 # Some versions of bitcoinrpc ServiceProxy have problems copying/pickling, so just store name and URI for now
478 newMerkleTree.source = TS['name']
479 newMerkleTree.source_uri = TS['uri']
481 (AcceptedScore, TotalScore) = self._CheckTemplate(newMerkleTree, TS)
482 if TotalScore is None:
483 return (0, newMerkleTree)
485 AcceptRatio = AcceptedScore / TotalScore
487 self.logger.debug('Template from \'%s\' has %s acceptance ratio and score of %s' % (TS['name'], AcceptRatio, AcceptedScore))
489 if AcceptRatio <= self.MinimumTemplateAcceptanceRatio:
492 if TotalScore < self.MinimumTemplateScore:
495 return (AcceptRatio, newMerkleTree)
497 def _updateMerkleTree_I(self):
499 for TSPriList in self.TemplateSources:
500 # FIXME: Implement weighting
501 for i in range(len(TSPriList)):
502 TS = TSPriList.pop(0)
506 r = self._updateMerkleTree_fromTS(TS)
515 if TSPriList == self.TemplateSources[-1] and i == len(TSPriList) - 1 and Best[1] is None:
518 self.logger.error(traceback.format_exc())
522 raise RuntimeError('Failed to create usable template')
524 self.logger.debug('Updating merkle tree with template from \'%s\'' % (BestMT.source,))
526 blkbasics = (MP['_prevBlock'], MP['height'], MP['_bits'])
527 if blkbasics != self.currentBlock:
528 self.updateBlock(*blkbasics, _HBH=(MP['previousblockhash'], MP['bits']))
529 self.currentMerkleTree = BestMT
531 def updateMerkleTree(self):
533 self.logger.debug('Polling for new block template')
534 self.nextMerkleUpdate = now + self.TxnUpdateRetryWait
536 self._updateMerkleTree_I()
538 self.lastMerkleUpdate = now
539 self.nextMerkleUpdate = now + self.MinimumTxnUpdateWait
541 if self.needMerkle == 2:
543 self.needMerkleSince = now
545 def makeCoinbase(self, height):
547 if now > _makeCoinbase[0]:
548 _makeCoinbase[0] = now
551 _makeCoinbase[1] += 1
552 rv = self.CoinbasePrefix
553 rv += pack('>L', now) + pack('>Q', _makeCoinbase[1]).lstrip(b'\0')
554 # NOTE: Not using varlenEncode, since this is always guaranteed to be < 100
555 rv = bytes( (len(rv),) ) + rv
556 for v in self.CoinbaseAux.values():
560 if self.overflowed < t - 300:
561 self.logger.warning('Overflowing coinbase data! %d bytes long' % (len(rv),))
563 self.isOverflowed = True
566 self.isOverflowed = False
567 rv = bitcoin.script.encodeUNum(height) + rv
570 def makeMerkleRoot(self, merkleTree, height):
571 cbtxn = merkleTree.data[0]
572 cb = self.makeCoinbase(height=height)
573 cbtxn.setCoinbase(cb)
575 merkleRoot = merkleTree.merkleRoot()
576 return (merkleRoot, merkleTree, cb)
579 def _doing(self, what):
580 if self._doing_last == what:
585 self.logger.debug("Switching from (%4dx in %5.3f seconds) %s => %s" % (self._doing_i, now - self._doing_s, self._doing_last, what))
586 self._doing_last = what
590 def _floodWarning(self, now, wid, wmsgf = None, doin = True, logf = None):
592 doin = self._doing_last
594 return lambda: "%s (doing %s)" % (f(), doin)
596 winfo = self.lastWarning.setdefault(wid, [0, None])
597 (lastTime, lastDoing) = winfo
598 if now <= lastTime + max(5, self.MinimumTxnUpdateWait):
604 logf = self.logger.warning
605 logf(wmsgf() if wmsgf else doin)
607 def _makeOne(self, putf, merkleTree, height):
608 MakingAtThisHeight = self.currentBlock[1]
609 MR = self.makeMerkleRoot(merkleTree, height=height)
610 # Only add it if the height hasn't changed in the meantime, to avoid a race
611 if self.currentBlock[1] == MakingAtThisHeight:
615 self._doing('clear merkle roots')
616 self._makeOne(self.clearMerkleRoots.put, self.curClearMerkleTree, height=self.currentBlock[1])
619 self._doing('longpoll merkle roots')
620 self._makeOne(self.nextMerkleRoots.put, self.nextMerkleTree, height=self.currentBlock[1] + 1)
622 def makeRegular(self):
623 self._doing('regular merkle roots')
624 self._makeOne(self.merkleRoots.append, self.currentMerkleTree, height=self.currentBlock[1])
626 def merkleMaker_II(self):
629 # No bits = no mining :(
631 return self.updateMerkleTree()
633 # First, ensure we have the minimum clear, next, and regular (in that order)
634 if self.clearMerkleRoots.qsize() < self.WorkQueueSizeClear[0]:
635 return self.makeClear()
636 if self.nextMerkleRoots.qsize() < self.WorkQueueSizeLongpoll[0]:
637 return self.makeNext()
638 if len(self.merkleRoots) < self.WorkQueueSizeRegular[0]:
639 return self.makeRegular()
641 # If we've met the minimum requirements, consider updating the merkle tree
642 if self.nextMerkleUpdate <= now:
643 return self.updateMerkleTree()
645 # Finally, fill up clear, next, and regular until we've met the maximums
646 if self.clearMerkleRoots.qsize() < self.WorkQueueSizeClear[1]:
647 return self.makeClear()
648 if self.nextMerkleRoots.qsize() < self.WorkQueueSizeLongpoll[1]:
649 return self.makeNext()
650 if len(self.merkleRoots) < self.WorkQueueSizeRegular[1] or self.merkleRoots[0][1] != self.currentMerkleTree:
651 return self.makeRegular()
653 # Nothing left to do, fire onBlockUpdate event (if appropriate) and sleep
654 if self.needMerkle == 1:
656 self.needMerkle = False
658 # TODO: rather than sleepspin, block until MinimumTxnUpdateWait expires or threading.Condition(?)
659 sleep(self.IdleSleepTime)
661 def merkleMaker_I(self):
665 self.merkleMaker_II()
667 if self.needMerkle == 1 and now > self.needMerkleSince + self.WarningDelayTxnLongpoll:
668 self._floodWarning(now, 'NeedMerkle', lambda: 'Transaction-longpoll requested %d seconds ago, and still not ready. Is your server fast enough to keep up with your configured WorkQueueSizeRegular maximum?' % (now - self.needMerkleSince,))
669 if now > self.nextMerkleUpdate + self.WarningDelayMerkleUpdate:
670 self._floodWarning(now, 'MerkleUpdate', lambda: "Haven't updated the merkle tree in at least %d seconds! Is your server fast enough to keep up with your configured work queue minimums?" % (now - self.lastMerkleUpdate,))
677 self.logger.critical(traceback.format_exc())
679 def start(self, *a, **k):
681 super().start(*a, **k)
685 MRD = self.merkleRoots.pop()
686 self.LowestMerkleRoots = min(len(self.merkleRoots), self.LowestMerkleRoots)
689 qsz = self.clearMerkleRoots.qsize()
691 self.logger.warning('clearMerkleRoots running out! only %d left' % (qsz,))
694 MRD = self.clearMerkleRoots.get()
695 self.LowestClearMerkleRoots = min(self.clearMerkleRoots.qsize(), self.LowestClearMerkleRoots)
697 (merkleRoot, merkleTree, cb) = MRD
698 (prevBlock, height, bits) = self.currentBlock
699 return (merkleRoot, merkleTree, cb, prevBlock, bits, rollPrevBlk)
701 def getMC(self, wantClear = False):
704 while not self.ready:
706 (prevBlock, height, bits) = self.currentBlock
707 mt = self.curClearMerkleTree if wantClear else self.currentMerkleTree
708 cb = self.makeCoinbase(height=height)
709 rollPrevBlk = (mt == self.curClearMerkleTree)
710 return (height, mt, cb, prevBlock, bits, rollPrevBlk)
717 reallogger = MM.logger
720 def critical(self, *a):
721 if self.LO > 1: return
722 reallogger.critical(*a)
723 def warning(self, *a):
725 reallogger.warning(*a)
728 MM.logger = fakelogger()
732 # _makeBlockSafe tests
733 from copy import deepcopy
737 txnlist = [b'\0', b'\x01', b'\x02']
738 txninfo = [{'fee':0, 'sigops':1}, {'fee':5, 'sigops':10000}, {'fee':0, 'sigops':10001}]
740 m = deepcopy( (MP, txnlist, txninfo) )
743 MM._makeBlockSafe(*m)
748 assert LO < 2 # An expected error wasn't thrown
749 if 'POTInfo' in m[0]:
753 assert MBS() == (MP, txnlist[:2], txninfo[:2])
754 txninfo[2]['fee'] = 1
756 MPx['coinbasevalue'] -= 1
757 assert MBS() == (MPx, txnlist[:2], txninfo[:2])
758 txninfo[2]['sigops'] = 1
759 assert MBS(1) == (MP, txnlist, txninfo)
762 txnlist.append(b'\x03')
763 txninfo.append({'fee':1, 'sigops':0})
765 MPx['coinbasevalue'] -= 1
766 assert MBS() == (MPx, txnlist[:3], txninfo[:3])
770 txninfo[1]['fee'] = 0
771 txninfo[2]['fee'] = 0
772 assert MBS(1) == (MP, txnlist, txninfo)
774 def makeCoinbaseTxn(coinbaseValue, useCoinbaser = True):
776 txn.addOutput(coinbaseValue, b'')
778 MM.makeCoinbaseTxn = makeCoinbaseTxn
779 MM.updateBlock = lambda *a, **ka: None
782 {'data': '11', 'depends': [], 'fee': 1, 'sigops': 1},
783 {'data': '11', 'depends': [], 'fee': 0, 'sigops': 1},
784 {'data': '11', 'depends': [], 'fee': 0, 'sigops': 1},
785 {'data': '11', 'depends': [], 'fee': 1, 'sigops': 2}
789 'previousblockhash': '000000000000012806bc100006dc83220bd9c2ac2709dc14a0d0fa1d6f9b733c',
793 nMT = MM._ProcessGBT(gbt)
794 assert len(nMT.data) == 5
795 nMT.data[0].disassemble()
796 assert sum(outp[0] for outp in nMT.data[0].outputs) == 3
798 nMT = MM._ProcessGBT(gbt)
799 assert len(nMT.data) in (2, 4)
800 nMT.data[0].disassemble()
801 assert sum(outp[0] for outp in nMT.data[0].outputs) == 2