Changeset View
Changeset View
Standalone View
Standalone View
test/functional/mining_basic.py
Show All 17 Lines | |||||
) | ) | ||||
from test_framework.mininode import ( | from test_framework.mininode import ( | ||||
P2PDataStore, | P2PDataStore, | ||||
) | ) | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | assert_equal, | ||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
bytes_to_hex_str as b2x, | |||||
) | ) | ||||
def assert_template(node, block, expect, rehash=True): | def assert_template(node, block, expect, rehash=True): | ||||
if rehash: | if rehash: | ||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
rsp = node.getblocktemplate( | rsp = node.getblocktemplate( | ||||
{'data': b2x(block.serialize()), 'mode': 'proposal'}) | {'data': block.serialize().hex(), 'mode': 'proposal'}) | ||||
assert_equal(rsp, expect) | assert_equal(rsp, expect) | ||||
class MiningTest(BitcoinTestFramework): | class MiningTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.setup_clean_chain = False | self.setup_clean_chain = False | ||||
def run_test(self): | def run_test(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
def assert_submitblock(block, result_str_1, result_str_2=None): | def assert_submitblock(block, result_str_1, result_str_2=None): | ||||
block.solve() | block.solve() | ||||
result_str_2 = result_str_2 or 'duplicate-invalid' | result_str_2 = result_str_2 or 'duplicate-invalid' | ||||
assert_equal(result_str_1, node.submitblock( | assert_equal(result_str_1, node.submitblock( | ||||
hexdata=b2x(block.serialize()))) | hexdata=block.serialize().hex())) | ||||
assert_equal(result_str_2, node.submitblock( | assert_equal(result_str_2, node.submitblock( | ||||
hexdata=b2x(block.serialize()))) | hexdata=block.serialize().hex())) | ||||
self.log.info('getmininginfo') | self.log.info('getmininginfo') | ||||
mining_info = node.getmininginfo() | mining_info = node.getmininginfo() | ||||
assert_equal(mining_info['blocks'], 200) | assert_equal(mining_info['blocks'], 200) | ||||
assert_equal(mining_info['chain'], 'regtest') | assert_equal(mining_info['chain'], 'regtest') | ||||
assert_equal(mining_info['currentblocksize'], 0) | assert_equal(mining_info['currentblocksize'], 0) | ||||
assert_equal(mining_info['currentblocktx'], 0) | assert_equal(mining_info['currentblocktx'], 0) | ||||
assert_equal(mining_info['difficulty'], | assert_equal(mining_info['difficulty'], | ||||
Show All 22 Lines | def run_test(self): | ||||
block.nNonce = 0 | block.nNonce = 0 | ||||
block.vtx = [coinbase_tx] | block.vtx = [coinbase_tx] | ||||
self.log.info("getblocktemplate: Test valid block") | self.log.info("getblocktemplate: Test valid block") | ||||
assert_template(node, block, None) | assert_template(node, block, None) | ||||
self.log.info("submitblock: Test block decode failure") | self.log.info("submitblock: Test block decode failure") | ||||
assert_raises_rpc_error(-22, "Block decode failed", | assert_raises_rpc_error(-22, "Block decode failed", | ||||
node.submitblock, b2x(block.serialize()[:-15])) | node.submitblock, block.serialize()[:-15].hex()) | ||||
self.log.info( | self.log.info( | ||||
"getblocktemplate: Test bad input hash for coinbase transaction") | "getblocktemplate: Test bad input hash for coinbase transaction") | ||||
bad_block = copy.deepcopy(block) | bad_block = copy.deepcopy(block) | ||||
bad_block.vtx[0].vin[0].prevout.hash += 1 | bad_block.vtx[0].vin[0].prevout.hash += 1 | ||||
bad_block.vtx[0].rehash() | bad_block.vtx[0].rehash() | ||||
assert_template(node, bad_block, 'bad-cb-missing') | assert_template(node, bad_block, 'bad-cb-missing') | ||||
self.log.info("submitblock: Test invalid coinbase transaction") | self.log.info("submitblock: Test invalid coinbase transaction") | ||||
assert_raises_rpc_error(-22, "Block does not start with a coinbase", | assert_raises_rpc_error(-22, "Block does not start with a coinbase", | ||||
node.submitblock, b2x(bad_block.serialize())) | node.submitblock, bad_block.serialize().hex()) | ||||
self.log.info("getblocktemplate: Test truncated final transaction") | self.log.info("getblocktemplate: Test truncated final transaction") | ||||
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, | assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { | ||||
{'data': b2x(block.serialize()[:-1]), 'mode': 'proposal'}) | 'data': block.serialize()[:-1].hex(), 'mode': 'proposal'}) | ||||
self.log.info("getblocktemplate: Test duplicate transaction") | self.log.info("getblocktemplate: Test duplicate transaction") | ||||
bad_block = copy.deepcopy(block) | bad_block = copy.deepcopy(block) | ||||
bad_block.vtx.append(bad_block.vtx[0]) | bad_block.vtx.append(bad_block.vtx[0]) | ||||
assert_template(node, bad_block, 'bad-txns-duplicate') | assert_template(node, bad_block, 'bad-txns-duplicate') | ||||
assert_submitblock(bad_block, 'bad-txns-duplicate', | assert_submitblock(bad_block, 'bad-txns-duplicate', | ||||
'bad-txns-duplicate') | 'bad-txns-duplicate') | ||||
Show All 14 Lines | def run_test(self): | ||||
assert_submitblock(bad_block, 'bad-txns-nonfinal') | assert_submitblock(bad_block, 'bad-txns-nonfinal') | ||||
self.log.info("getblocktemplate: Test bad tx count") | self.log.info("getblocktemplate: Test bad tx count") | ||||
# The tx count is immediately after the block header | # The tx count is immediately after the block header | ||||
TX_COUNT_OFFSET = 80 | TX_COUNT_OFFSET = 80 | ||||
bad_block_sn = bytearray(block.serialize()) | bad_block_sn = bytearray(block.serialize()) | ||||
assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) | assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) | ||||
bad_block_sn[TX_COUNT_OFFSET] += 1 | bad_block_sn[TX_COUNT_OFFSET] += 1 | ||||
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, | assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { | ||||
{'data': b2x(bad_block_sn), 'mode': 'proposal'}) | 'data': bad_block_sn.hex(), 'mode': 'proposal'}) | ||||
self.log.info("getblocktemplate: Test bad bits") | self.log.info("getblocktemplate: Test bad bits") | ||||
bad_block = copy.deepcopy(block) | bad_block = copy.deepcopy(block) | ||||
bad_block.nBits = 469762303 # impossible in the real world | bad_block.nBits = 469762303 # impossible in the real world | ||||
assert_template(node, bad_block, 'bad-diffbits') | assert_template(node, bad_block, 'bad-diffbits') | ||||
self.log.info("getblocktemplate: Test bad merkle root") | self.log.info("getblocktemplate: Test bad merkle root") | ||||
bad_block = copy.deepcopy(block) | bad_block = copy.deepcopy(block) | ||||
Show All 27 Lines | def run_test(self): | ||||
block.nTime += 1 | block.nTime += 1 | ||||
block.solve() | block.solve() | ||||
def chain_tip(b_hash, *, status='headers-only', branchlen=1): | def chain_tip(b_hash, *, status='headers-only', branchlen=1): | ||||
return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status} | return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status} | ||||
assert chain_tip(block.hash) not in node.getchaintips() | assert chain_tip(block.hash) not in node.getchaintips() | ||||
node.submitheader(hexdata=b2x(block.serialize())) | node.submitheader(hexdata=block.serialize().hex()) | ||||
assert chain_tip(block.hash) in node.getchaintips() | assert chain_tip(block.hash) in node.getchaintips() | ||||
# Noop | # Noop | ||||
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) | node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) | ||||
assert chain_tip(block.hash) in node.getchaintips() | assert chain_tip(block.hash) in node.getchaintips() | ||||
bad_block_root = copy.deepcopy(block) | bad_block_root = copy.deepcopy(block) | ||||
bad_block_root.hashMerkleRoot += 2 | bad_block_root.hashMerkleRoot += 2 | ||||
bad_block_root.solve() | bad_block_root.solve() | ||||
assert chain_tip(bad_block_root.hash) not in node.getchaintips() | assert chain_tip(bad_block_root.hash) not in node.getchaintips() | ||||
node.submitheader(hexdata=b2x( | node.submitheader(hexdata=CBlockHeader( | ||||
CBlockHeader(bad_block_root).serialize())) | bad_block_root).serialize().hex()) | ||||
assert chain_tip(bad_block_root.hash) in node.getchaintips() | assert chain_tip(bad_block_root.hash) in node.getchaintips() | ||||
# Should still reject invalid blocks, even if we have the header: | # Should still reject invalid blocks, even if we have the header: | ||||
assert_equal(node.submitblock(hexdata=b2x( | assert_equal(node.submitblock( | ||||
bad_block_root.serialize())), 'bad-txnmrklroot') | hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') | ||||
assert_equal(node.submitblock(hexdata=b2x( | assert_equal(node.submitblock( | ||||
bad_block_root.serialize())), 'bad-txnmrklroot') | hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') | ||||
assert chain_tip(bad_block_root.hash) in node.getchaintips() | assert chain_tip(bad_block_root.hash) in node.getchaintips() | ||||
# We know the header for this invalid block, so should just return early without error: | # We know the header for this invalid block, so should just return early without error: | ||||
node.submitheader(hexdata=b2x( | node.submitheader(hexdata=CBlockHeader( | ||||
CBlockHeader(bad_block_root).serialize())) | bad_block_root).serialize().hex()) | ||||
assert chain_tip(bad_block_root.hash) in node.getchaintips() | assert chain_tip(bad_block_root.hash) in node.getchaintips() | ||||
bad_block_lock = copy.deepcopy(block) | bad_block_lock = copy.deepcopy(block) | ||||
bad_block_lock.vtx[0].nLockTime = 2**32 - 1 | bad_block_lock.vtx[0].nLockTime = 2**32 - 1 | ||||
bad_block_lock.vtx[0].rehash() | bad_block_lock.vtx[0].rehash() | ||||
bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() | bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() | ||||
bad_block_lock.solve() | bad_block_lock.solve() | ||||
assert_equal(node.submitblock(hexdata=b2x( | assert_equal(node.submitblock( | ||||
bad_block_lock.serialize())), 'bad-txns-nonfinal') | hexdata=bad_block_lock.serialize().hex()), 'bad-txns-nonfinal') | ||||
assert_equal(node.submitblock(hexdata=b2x( | assert_equal(node.submitblock( | ||||
bad_block_lock.serialize())), 'duplicate-invalid') | hexdata=bad_block_lock.serialize().hex()), 'duplicate-invalid') | ||||
# Build a "good" block on top of the submitted bad block | # Build a "good" block on top of the submitted bad block | ||||
bad_block2 = copy.deepcopy(block) | bad_block2 = copy.deepcopy(block) | ||||
bad_block2.hashPrevBlock = bad_block_lock.sha256 | bad_block2.hashPrevBlock = bad_block_lock.sha256 | ||||
bad_block2.solve() | bad_block2.solve() | ||||
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader( | assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader( | ||||
hexdata=b2x(CBlockHeader(bad_block2).serialize()))) | hexdata=CBlockHeader(bad_block2).serialize().hex())) | ||||
# Should reject invalid header right away | # Should reject invalid header right away | ||||
bad_block_time = copy.deepcopy(block) | bad_block_time = copy.deepcopy(block) | ||||
bad_block_time.nTime = 1 | bad_block_time.nTime = 1 | ||||
bad_block_time.solve() | bad_block_time.solve() | ||||
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader( | assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader( | ||||
hexdata=b2x(CBlockHeader(bad_block_time).serialize()))) | hexdata=CBlockHeader(bad_block_time).serialize().hex())) | ||||
# Should ask for the block from a p2p node, if they announce the header as well: | # Should ask for the block from a p2p node, if they announce the header as well: | ||||
node.add_p2p_connection(P2PDataStore()) | node.add_p2p_connection(P2PDataStore()) | ||||
# Drop the first getheaders | # Drop the first getheaders | ||||
node.p2p.wait_for_getheaders(timeout=5) | node.p2p.wait_for_getheaders(timeout=5) | ||||
node.p2p.send_blocks_and_test(blocks=[block], node=node) | node.p2p.send_blocks_and_test(blocks=[block], node=node) | ||||
# Must be active now: | # Must be active now: | ||||
assert chain_tip(block.hash, status='active', | assert chain_tip(block.hash, status='active', | ||||
branchlen=0) in node.getchaintips() | branchlen=0) in node.getchaintips() | ||||
# Building a few blocks should give the same results | # Building a few blocks should give the same results | ||||
node.generate(10) | node.generate(10) | ||||
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader( | assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader( | ||||
hexdata=b2x(CBlockHeader(bad_block_time).serialize()))) | hexdata=CBlockHeader(bad_block_time).serialize().hex())) | ||||
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader( | assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader( | ||||
hexdata=b2x(CBlockHeader(bad_block2).serialize()))) | hexdata=CBlockHeader(bad_block2).serialize().hex())) | ||||
node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) | node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) | ||||
node.submitheader(hexdata=b2x( | node.submitheader(hexdata=CBlockHeader( | ||||
CBlockHeader(bad_block_root).serialize())) | bad_block_root).serialize().hex()) | ||||
# valid | # valid | ||||
assert_equal(node.submitblock(hexdata=b2x( | assert_equal(node.submitblock( | ||||
block.serialize())), 'duplicate') | hexdata=block.serialize().hex()), 'duplicate') | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
MiningTest().main() | MiningTest().main() |