Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-invalid-chains.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2019 The Bitcoin developers | # Copyright (c) 2019 The Bitcoin developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-ilncense.php. | # file COPYING or http://www.opensource.org/licenses/mit-ilncense.php. | ||||
import time | import time | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.comptool import RejectResult, TestInstance, TestManager | from test_framework.mininode import network_thread_start, P2PDataStore | ||||
from test_framework.mininode import network_thread_start | |||||
from test_framework.util import assert_equal | from test_framework.util import assert_equal | ||||
from test_framework.blocktools import ( | from test_framework.blocktools import ( | ||||
create_block, | create_block, | ||||
create_coinbase, | create_coinbase, | ||||
) | ) | ||||
class InvalidChainsTest(BitcoinTestFramework): | class InvalidChainsTest(BitcoinTestFramework): | ||||
Show All 21 Lines | def next_block(self, number): | ||||
block.solve() | block.solve() | ||||
self.tip = block | self.tip = block | ||||
self.block_heights[block.sha256] = height | self.block_heights[block.sha256] = height | ||||
assert number not in self.blocks | assert number not in self.blocks | ||||
self.blocks[number] = block | self.blocks[number] = block | ||||
return block | return block | ||||
def run_test(self): | def run_test(self): | ||||
self.test = TestManager(self, self.options.tmpdir) | node = self.nodes[0] | ||||
self.test.add_all_connections(self.nodes) | node.add_p2p_connection(P2PDataStore()) | ||||
network_thread_start() | network_thread_start() | ||||
self.test.run() | node.p2p.wait_for_verack() | ||||
def get_tests(self): | |||||
node = self.nodes[0] | |||||
self.genesis_hash = int(node.getbestblockhash(), 16) | self.genesis_hash = int(node.getbestblockhash(), 16) | ||||
self.block_heights[self.genesis_hash] = 0 | self.block_heights[self.genesis_hash] = 0 | ||||
# returns a test case that asserts that the current tip was accepted | |||||
def accepted(expectedTipHash=None): | |||||
if expectedTipHash is None: | |||||
return TestInstance([[self.tip, True]]) | |||||
else: | |||||
return TestInstance([[self.tip, True, expectedTipHash]]) | |||||
# returns a test case that asserts that the current tip was rejected | |||||
def rejected(reject=None): | |||||
if reject is None: | |||||
return TestInstance([[self.tip, False]]) | |||||
else: | |||||
return TestInstance([[self.tip, reject]]) | |||||
# move the tip back to a previous block | # move the tip back to a previous block | ||||
def tip(number): | def tip(number): | ||||
self.tip = self.blocks[number] | self.tip = self.blocks[number] | ||||
# shorthand for functions | # shorthand for functions | ||||
block = self.next_block | block = self.next_block | ||||
# Reference for blocks mined in this test: | # Reference for blocks mined in this test: | ||||
# | # | ||||
# 11 21 -- 221 - 222 | # 11 21 -- 221 - 222 | ||||
# / / / | # / / / | ||||
# 0 - 1 - 2 - 22 - 23 - 24 - 25 | # 0 - 1 - 2 - 22 - 23 - 24 - 25 | ||||
# \ | # \ | ||||
# -- 12 - 13 - 14 | # -- 12 - 13 - 14 | ||||
# Generate some valid blocks | # Generate some valid blocks | ||||
block(0) | node.p2p.send_blocks_and_test([block(0), block(1), block(2)], node) | ||||
yield accepted() | |||||
block(1) | |||||
yield accepted() | |||||
block(2) | |||||
yield accepted() | |||||
# Explicitly invalidate blocks 1 and 2 | # Explicitly invalidate blocks 1 and 2 | ||||
# See below for why we do this | # See below for why we do this | ||||
node.invalidateblock(self.blocks[1].hash) | node.invalidateblock(self.blocks[1].hash) | ||||
assert_equal(self.blocks[0].hash, node.getbestblockhash()) | assert_equal(self.blocks[0].hash, node.getbestblockhash()) | ||||
node.invalidateblock(self.blocks[2].hash) | node.invalidateblock(self.blocks[2].hash) | ||||
assert_equal(self.blocks[0].hash, node.getbestblockhash()) | assert_equal(self.blocks[0].hash, node.getbestblockhash()) | ||||
# Mining on top of blocks 1 or 2 is rejected | # Mining on top of blocks 1 or 2 is rejected | ||||
tip(1) | tip(1) | ||||
block(11) | node.p2p.send_blocks_and_test( | ||||
yield rejected(RejectResult(16, b'bad-prevblk')) | [block(11)], node, success=False, reject_reason='bad-prevblk', request_block=False) | ||||
tip(2) | tip(2) | ||||
block(21) | node.p2p.send_blocks_and_test( | ||||
yield rejected(RejectResult(16, b'bad-prevblk')) | [block(21)], node, success=False, reject_reason='bad-prevblk', request_block=False) | ||||
# Reconsider block 2 to remove invalid status from *both* 1 and 2 | # Reconsider block 2 to remove invalid status from *both* 1 and 2 | ||||
# The goal is to test that block 1 is not retaining any internal state | # The goal is to test that block 1 is not retaining any internal state | ||||
# that prevents us from accepting blocks building on top of block 1 | # that prevents us from accepting blocks building on top of block 1 | ||||
node.reconsiderblock(self.blocks[2].hash) | node.reconsiderblock(self.blocks[2].hash) | ||||
assert_equal(self.blocks[2].hash, node.getbestblockhash()) | assert_equal(self.blocks[2].hash, node.getbestblockhash()) | ||||
# Mining on the block 1 chain should be accepted | # Mining on the block 1 chain should be accepted | ||||
# (needs to mine two blocks because less-work chains are not processed) | # (needs to mine two blocks because less-work chains are not processed) | ||||
test = TestInstance(sync_every_block=False) | |||||
tip(1) | tip(1) | ||||
block(12) | node.p2p.send_blocks_and_test([block(12), block(13)], node) | ||||
test.blocks_and_transactions.append([self.tip, None]) | |||||
block(13) | |||||
test.blocks_and_transactions.append([self.tip, None]) | |||||
yield test | |||||
# Mining on the block 2 chain should still be accepted | # Mining on the block 2 chain should still be accepted | ||||
# (needs to mine two blocks because less-work chains are not processed) | # (needs to mine two blocks because less-work chains are not processed) | ||||
test = TestInstance(sync_every_block=False) | |||||
tip(2) | tip(2) | ||||
block(22) | node.p2p.send_blocks_and_test([block(22), block(221)], node) | ||||
test.blocks_and_transactions.append([self.tip, None]) | |||||
# Mine block 221 for later | |||||
block(221) | |||||
test.blocks_and_transactions.append([self.tip, None]) | |||||
yield test | |||||
# Mine more blocks from block 22 to be longest chain | # Mine more blocks from block 22 to be longest chain | ||||
test = TestInstance(sync_every_block=False) | |||||
tip(22) | tip(22) | ||||
block(23) | node.p2p.send_blocks_and_test([block(23), block(24)], node) | ||||
test.blocks_and_transactions.append([self.tip, None]) | |||||
block(24) | |||||
test.blocks_and_transactions.append([self.tip, None]) | |||||
yield test | |||||
# Sanity checks | # Sanity checks | ||||
assert_equal(self.blocks[24].hash, node.getbestblockhash()) | assert_equal(self.blocks[24].hash, node.getbestblockhash()) | ||||
assert any(self.blocks[221].hash == chaintip["hash"] | assert any(self.blocks[221].hash == chaintip["hash"] | ||||
for chaintip in node.getchaintips()) | for chaintip in node.getchaintips()) | ||||
# Invalidating the block 2 chain should reject new blocks on that chain | # Invalidating the block 2 chain should reject new blocks on that chain | ||||
node.invalidateblock(self.blocks[2].hash) | node.invalidateblock(self.blocks[2].hash) | ||||
assert_equal(self.blocks[13].hash, node.getbestblockhash()) | assert_equal(self.blocks[13].hash, node.getbestblockhash()) | ||||
# Mining on the block 2 chain should be rejected | # Mining on the block 2 chain should be rejected | ||||
tip(24) | tip(24) | ||||
block(25) | node.p2p.send_blocks_and_test( | ||||
yield rejected(RejectResult(16, b'bad-prevblk')) | [block(25)], node, success=False, reject_reason='bad-prevblk', request_block=False) | ||||
# Continued mining on the block 1 chain is still ok | # Continued mining on the block 1 chain is still ok | ||||
tip(13) | tip(13) | ||||
block(14) | node.p2p.send_blocks_and_test([block(14)], node) | ||||
yield accepted() | |||||
# Mining on a once-valid chain forking from block 2's longest chain, | # Mining on a once-valid chain forking from block 2's longest chain, | ||||
# which is now invalid, should also be rejected. | # which is now invalid, should also be rejected. | ||||
tip(221) | tip(221) | ||||
block(222) | node.p2p.send_blocks_and_test( | ||||
yield rejected(RejectResult(16, b'bad-prevblk')) | [block(222)], node, success=False, reject_reason='bad-prevblk', request_block=False) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
InvalidChainsTest().main() | InvalidChainsTest().main() |