#!/usr/bin/env python2 # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from test_framework import BitcoinTestFramework from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException from util import * from binascii import a2b_hex, b2a_hex from hashlib import sha256 from struct import pack def check_array_result(object_array, to_match, expected): """ Pass in array of JSON objects, a dictionary with key/value pairs to match against, and another dictionary with expected key/value pairs. """ num_matched = 0 for item in object_array: all_match = True for key,value in to_match.items(): if item[key] != value: all_match = False if not all_match: continue for key,value in expected.items(): if item[key] != value: raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value))) num_matched = num_matched+1 if num_matched == 0: raise AssertionError("No objects matched %s"%(str(to_match))) def b2x(b): return b2a_hex(b).decode('ascii') # NOTE: This does not work for signed numbers (set the high bit) or zero (use b'\0') def encodeUNum(n): s = bytearray(b'\1') while n > 127: s[0] += 1 s.append(n % 256) n //= 256 s.append(n) return bytes(s) def varlenEncode(n): if n < 0xfd: return pack('<B', n) if n <= 0xffff: return b'\xfd' + pack('<H', n) if n <= 0xffffffff: return b'\xfe' + pack('<L', n) return b'\xff' + pack('<Q', n) def dblsha(b): return sha256(sha256(b).digest()).digest() def genmrklroot(leaflist): cur = leaflist while len(cur) > 1: n = [] if len(cur) & 1: cur.append(cur[-1]) for i in range(0, len(cur), 2): n.append(dblsha(cur[i] + cur[i+1])) cur = n return cur[0] def template_to_bytes(tmpl, txlist): blkver = pack('<L', tmpl['version']) mrklroot = genmrklroot(list(dblsha(a) for a in txlist)) timestamp = pack('<L', tmpl['curtime']) nonce = b'\0\0\0\0' blk = blkver + a2b_hex(tmpl['previousblockhash'])[::-1] + mrklroot + timestamp + a2b_hex(tmpl['bits'])[::-1] + nonce blk += varlenEncode(len(txlist)) for tx in txlist: blk += tx return blk def template_to_hex(tmpl, txlist): return b2x(template_to_bytes(tmpl, txlist)) def assert_template(node, tmpl, txlist, expect): rsp = node.getblocktemplate({'data':template_to_hex(tmpl, txlist),'mode':'proposal'}) if rsp != expect: raise AssertionError('unexpected: %s' % (rsp,)) class GetBlockTemplateProposalTest(BitcoinTestFramework): ''' Test block proposals with getblocktemplate. ''' def run_test(self): node = self.nodes[0] node.setgenerate(True, 1) # Mine a block to leave initial block download tmpl = node.getblocktemplate() if 'coinbasetxn' not in tmpl: rawcoinbase = encodeUNum(tmpl['height']) rawcoinbase += b'\x01-' hexcoinbase = b2x(rawcoinbase) hexoutval = b2x(pack('<Q', tmpl['coinbasevalue'])) tmpl['coinbasetxn'] = {'data': '01000000' + '01' + '0000000000000000000000000000000000000000000000000000000000000000ffffffff' + ('%02x' % (len(rawcoinbase),)) + hexcoinbase + 'fffffffe' + '01' + hexoutval + '00' + '00000000'} txlist = list(bytearray(a2b_hex(a['data'])) for a in (tmpl['coinbasetxn'],) + tuple(tmpl['transactions'])) # Test 0: Capability advertised assert('proposal' in tmpl['capabilities']) # NOTE: This test currently FAILS (regtest mode doesn't enforce block height in coinbase) ## Test 1: Bad height in coinbase #txlist[0][4+1+36+1+1] += 1 #assert_template(node, tmpl, txlist, 'FIXME') #txlist[0][4+1+36+1+1] -= 1 # Test 2: Bad input hash for gen tx txlist[0][4+1] += 1 assert_template(node, tmpl, txlist, 'bad-cb-missing') txlist[0][4+1] -= 1 # Test 3: Truncated final tx lastbyte = txlist[-1].pop() try: assert_template(node, tmpl, txlist, 'n/a') except JSONRPCException: pass # Expected txlist[-1].append(lastbyte) # Test 4: Add an invalid tx to the end (duplicate of gen tx) txlist.append(txlist[0]) assert_template(node, tmpl, txlist, 'bad-txns-duplicate') txlist.pop() # Test 5: Add an invalid tx to the end (non-duplicate) txlist.append(bytearray(txlist[0])) txlist[-1][4+1] = b'\xff' assert_template(node, tmpl, txlist, 'bad-txns-inputs-missingorspent') txlist.pop() # Test 6: Future tx lock time txlist[0][-4:] = b'\xff\xff\xff\xff' assert_template(node, tmpl, txlist, 'bad-txns-nonfinal') txlist[0][-4:] = b'\0\0\0\0' # Test 7: Bad tx count txlist.append(b'') try: assert_template(node, tmpl, txlist, 'n/a') except JSONRPCException: pass # Expected txlist.pop() # Test 8: Bad bits realbits = tmpl['bits'] tmpl['bits'] = '1c0000ff' # impossible in the real world assert_template(node, tmpl, txlist, 'bad-diffbits') tmpl['bits'] = realbits # Test 9: Bad merkle root rawtmpl = template_to_bytes(tmpl, txlist) rawtmpl[4+32] = (rawtmpl[4+32] + 1) % 0x100 rsp = node.getblocktemplate({'data':b2x(rawtmpl),'mode':'proposal'}) if rsp != 'bad-txnmrklroot': raise AssertionError('unexpected: %s' % (rsp,)) # Test 10: Bad timestamps realtime = tmpl['curtime'] tmpl['curtime'] = 0x7fffffff assert_template(node, tmpl, txlist, 'time-too-new') tmpl['curtime'] = 0 assert_template(node, tmpl, txlist, 'time-too-old') tmpl['curtime'] = realtime # Test 11: Valid block assert_template(node, tmpl, txlist, None) # Test 12: Orphan block tmpl['previousblockhash'] = 'ff00' * 16 assert_template(node, tmpl, txlist, 'inconclusive-not-best-prevblk') if __name__ == '__main__': GetBlockTemplateProposalTest().main()