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):
351 prevBlock = bytes.fromhex(MP['previousblockhash'])[::-1]
352 if 'height' not in MP:
353 MP['height'] = self.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 oMP.get('capabilities', ()):
396 return (0, newMerkleTree)
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)
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)
483 AcceptRatio = AcceptedScore / TotalScore
485 self.logger.debug('Template from \'%s\' has %s acceptance ratio and score of %s' % (TS['name'], AcceptRatio, AcceptedScore))
487 if AcceptRatio <= self.MinimumTemplateAcceptanceRatio:
490 if TotalScore < self.MinimumTemplateScore:
493 return (AcceptRatio, newMerkleTree)
495 def _updateMerkleTree_I(self):
497 for TSPriList in self.TemplateSources:
498 # FIXME: Implement weighting
499 for i in range(len(TSPriList)):
500 TS = TSPriList.pop(0)
504 r = self._updateMerkleTree_fromTS(TS)
513 if TSPriList == self.TemplateSources[-1] and i == len(TSPriList) - 1 and Best[1] is None:
516 self.logger.error(traceback.format_exc())
520 raise RuntimeError('Failed to create usable template')
522 self.logger.debug('Updating merkle tree with template from \'%s\'' % (BestMT.source,))
524 blkbasics = (MP['_prevBlock'], MP['height'], MP['_bits'])
525 if blkbasics != self.currentBlock:
526 self.updateBlock(*blkbasics, _HBH=(MP['previousblockhash'], MP['bits']))
527 self.currentMerkleTree = BestMT
529 def updateMerkleTree(self):
531 self.logger.debug('Polling for new block template')
532 self.nextMerkleUpdate = now + self.TxnUpdateRetryWait
534 self._updateMerkleTree_I()
536 self.lastMerkleUpdate = now
537 self.nextMerkleUpdate = now + self.MinimumTxnUpdateWait
539 if self.needMerkle == 2:
541 self.needMerkleSince = now
543 def makeCoinbase(self, height):
545 if now > _makeCoinbase[0]:
546 _makeCoinbase[0] = now
549 _makeCoinbase[1] += 1
550 rv = self.CoinbasePrefix
551 rv += pack('>L', now) + pack('>Q', _makeCoinbase[1]).lstrip(b'\0')
552 # NOTE: Not using varlenEncode, since this is always guaranteed to be < 100
553 rv = bytes( (len(rv),) ) + rv
554 for v in self.CoinbaseAux.values():
558 if self.overflowed < t - 300:
559 self.logger.warning('Overflowing coinbase data! %d bytes long' % (len(rv),))
561 self.isOverflowed = True
564 self.isOverflowed = False
565 rv = bitcoin.script.encodeUNum(height) + rv
568 def makeMerkleRoot(self, merkleTree, height):
569 cbtxn = merkleTree.data[0]
570 cb = self.makeCoinbase(height=height)
571 cbtxn.setCoinbase(cb)
573 merkleRoot = merkleTree.merkleRoot()
574 return (merkleRoot, merkleTree, cb)
577 def _doing(self, what):
578 if self._doing_last == what:
583 self.logger.debug("Switching from (%4dx in %5.3f seconds) %s => %s" % (self._doing_i, now - self._doing_s, self._doing_last, what))
584 self._doing_last = what
588 def _floodWarning(self, now, wid, wmsgf = None, doin = True, logf = None):
590 doin = self._doing_last
592 return lambda: "%s (doing %s)" % (f(), doin)
594 winfo = self.lastWarning.setdefault(wid, [0, None])
595 (lastTime, lastDoing) = winfo
596 if now <= lastTime + max(5, self.MinimumTxnUpdateWait):
602 logf = self.logger.warning
603 logf(wmsgf() if wmsgf else doin)
605 def _makeOne(self, putf, merkleTree, height):
606 MakingAtThisHeight = self.currentBlock[1]
607 MR = self.makeMerkleRoot(merkleTree, height=height)
608 # Only add it if the height hasn't changed in the meantime, to avoid a race
609 if self.currentBlock[1] == MakingAtThisHeight:
613 self._doing('clear merkle roots')
614 self._makeOne(self.clearMerkleRoots.put, self.curClearMerkleTree, height=self.currentBlock[1])
617 self._doing('longpoll merkle roots')
618 self._makeOne(self.nextMerkleRoots.put, self.nextMerkleTree, height=self.currentBlock[1] + 1)
620 def makeRegular(self):
621 self._doing('regular merkle roots')
622 self._makeOne(self.merkleRoots.append, self.currentMerkleTree, height=self.currentBlock[1])
624 def merkleMaker_II(self):
627 # No bits = no mining :(
629 return self.updateMerkleTree()
631 # First, ensure we have the minimum clear, next, and regular (in that order)
632 if self.clearMerkleRoots.qsize() < self.WorkQueueSizeClear[0]:
633 return self.makeClear()
634 if self.nextMerkleRoots.qsize() < self.WorkQueueSizeLongpoll[0]:
635 return self.makeNext()
636 if len(self.merkleRoots) < self.WorkQueueSizeRegular[0]:
637 return self.makeRegular()
639 # If we've met the minimum requirements, consider updating the merkle tree
640 if self.nextMerkleUpdate <= now:
641 return self.updateMerkleTree()
643 # Finally, fill up clear, next, and regular until we've met the maximums
644 if self.clearMerkleRoots.qsize() < self.WorkQueueSizeClear[1]:
645 return self.makeClear()
646 if self.nextMerkleRoots.qsize() < self.WorkQueueSizeLongpoll[1]:
647 return self.makeNext()
648 if len(self.merkleRoots) < self.WorkQueueSizeRegular[1] or self.merkleRoots[0][1] != self.currentMerkleTree:
649 return self.makeRegular()
651 # Nothing left to do, fire onBlockUpdate event (if appropriate) and sleep
652 if self.needMerkle == 1:
654 self.needMerkle = False
656 # TODO: rather than sleepspin, block until MinimumTxnUpdateWait expires or threading.Condition(?)
657 sleep(self.IdleSleepTime)
659 def merkleMaker_I(self):
663 self.merkleMaker_II()
665 if self.needMerkle == 1 and now > self.needMerkleSince + self.WarningDelayTxnLongpoll:
666 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,))
667 if now > self.nextMerkleUpdate + self.WarningDelayMerkleUpdate:
668 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,))
675 self.logger.critical(traceback.format_exc())
677 def start(self, *a, **k):
679 super().start(*a, **k)
683 MRD = self.merkleRoots.pop()
684 self.LowestMerkleRoots = min(len(self.merkleRoots), self.LowestMerkleRoots)
687 qsz = self.clearMerkleRoots.qsize()
689 self.logger.warning('clearMerkleRoots running out! only %d left' % (qsz,))
692 MRD = self.clearMerkleRoots.get()
693 self.LowestClearMerkleRoots = min(self.clearMerkleRoots.qsize(), self.LowestClearMerkleRoots)
695 (merkleRoot, merkleTree, cb) = MRD
696 (prevBlock, height, bits) = self.currentBlock
697 return (merkleRoot, merkleTree, cb, prevBlock, bits, rollPrevBlk)
699 def getMC(self, wantClear = False):
702 while not self.ready:
704 (prevBlock, height, bits) = self.currentBlock
705 mt = self.curClearMerkleTree if wantClear else self.currentMerkleTree
706 cb = self.makeCoinbase(height=height)
707 rollPrevBlk = (mt == self.curClearMerkleTree)
708 return (height, mt, cb, prevBlock, bits, rollPrevBlk)
715 reallogger = MM.logger
718 def critical(self, *a):
719 if self.LO > 1: return
720 reallogger.critical(*a)
721 def warning(self, *a):
723 reallogger.warning(*a)
726 MM.logger = fakelogger()
730 # _makeBlockSafe tests
731 from copy import deepcopy
735 txnlist = [b'\0', b'\x01', b'\x02']
736 txninfo = [{'fee':0, 'sigops':1}, {'fee':5, 'sigops':10000}, {'fee':0, 'sigops':10001}]
738 m = deepcopy( (MP, txnlist, txninfo) )
741 MM._makeBlockSafe(*m)
746 assert LO < 2 # An expected error wasn't thrown
747 if 'POTInfo' in m[0]:
751 assert MBS() == (MP, txnlist[:2], txninfo[:2])
752 txninfo[2]['fee'] = 1
754 MPx['coinbasevalue'] -= 1
755 assert MBS() == (MPx, txnlist[:2], txninfo[:2])
756 txninfo[2]['sigops'] = 1
757 assert MBS(1) == (MP, txnlist, txninfo)
760 txnlist.append(b'\x03')
761 txninfo.append({'fee':1, 'sigops':0})
763 MPx['coinbasevalue'] -= 1
764 assert MBS() == (MPx, txnlist[:3], txninfo[:3])
768 txninfo[1]['fee'] = 0
769 txninfo[2]['fee'] = 0
770 assert MBS(1) == (MP, txnlist, txninfo)
772 def makeCoinbaseTxn(coinbaseValue, useCoinbaser = True):
774 txn.addOutput(coinbaseValue, b'')
776 MM.makeCoinbaseTxn = makeCoinbaseTxn
777 MM.updateBlock = lambda *a, **ka: None
780 {'data': '11', 'depends': [], 'fee': 1, 'sigops': 1},
781 {'data': '11', 'depends': [], 'fee': 0, 'sigops': 1},
782 {'data': '11', 'depends': [], 'fee': 0, 'sigops': 1},
783 {'data': '11', 'depends': [], 'fee': 1, 'sigops': 2}
787 'previousblockhash': '000000000000012806bc100006dc83220bd9c2ac2709dc14a0d0fa1d6f9b733c',
791 nMT = MM._ProcessGBT(gbt)
792 assert len(nMT.data) == 5
793 nMT.data[0].disassemble()
794 assert sum(outp[0] for outp in nMT.data[0].outputs) == 3
796 nMT = MM._ProcessGBT(gbt)
797 assert len(nMT.data) in (2, 4)
798 nMT.data[0].disassemble()
799 assert sum(outp[0] for outp in nMT.data[0].outputs) == 2