Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-finalize-block.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2018 The Bitcoin developers | # Copyright (c) 2018 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-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
"""Test the finalizeblock RPC calls.""" | """Test the finalizeblock RPC calls.""" | ||||
import os | import os | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, wait_until | from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, wait_until | ||||
RPC_FINALIZE_INVALID_BLOCK_ERROR = 'finalize-invalid-block' | RPC_FINALIZE_INVALID_BLOCK_ERROR = 'finalize-invalid-block' | ||||
RPC_BLOCK_NOT_FOUND_ERROR = 'Block not found' | |||||
AUTO_FINALIZATION_DEPTH = 10 | AUTO_FINALIZATION_DEPTH = 10 | ||||
class FinalizeBlockTest(BitcoinTestFramework): | class FinalizeBlockTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_flags = [["-maxreorgdepth={}".format(AUTO_FINALIZATION_DEPTH)], [ | self.extra_flags = [["-maxreorgdepth={}".format(AUTO_FINALIZATION_DEPTH)], [ | ||||
"-maxreorgdepth={}".format(AUTO_FINALIZATION_DEPTH)]] | "-maxreorgdepth={}".format(AUTO_FINALIZATION_DEPTH)]] | ||||
Show All 19 Lines | def run_test(self): | ||||
alt_node = self.nodes[1] | alt_node = self.nodes[1] | ||||
connect_nodes_bi(self.nodes, 0, 1) | connect_nodes_bi(self.nodes, 0, 1) | ||||
sync_blocks(self.nodes[0:2]) | sync_blocks(self.nodes[0:2]) | ||||
alt_node.invalidateblock(tip) | alt_node.invalidateblock(tip) | ||||
# We will use this later to check auto-finalization during a reorg | # We will use this later to check auto-finalization during a reorg | ||||
auto_finalized_tip = alt_node.getbestblockhash() | auto_finalized_tip = alt_node.getbestblockhash() | ||||
alt_node.generate(10) | |||||
# Wait for node 0 to invalidate the chain. | # Node 0 should not accept the whole alt_node's chain due to tip being finalized, | ||||
def wait_for_invalid_block(node, block): | # even though it is longer. | ||||
# Headers would not be accepted if previousblock is invalid: | |||||
# - First block from alt node has same height than node tip, but is on a minority chain. Its | |||||
# status is "valid-headers" | |||||
# - Second block from alt node has height > node tip height, will be marked as invalid because | |||||
# node tip is finalized | |||||
# - Later blocks from alt node will be rejected because their previous block are invalid | |||||
# | |||||
# Expected state: | |||||
# | |||||
# On alt_node: | |||||
# >(210)->(211)-> // ->(218 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(209)->(210 invalid) | |||||
# | |||||
# On node: | |||||
# >(210 valid-headers)->(211 invalid)->(212 to 218 dropped) | |||||
# / | |||||
# (200)->(201)-> // ->(209)->(210 finalized, tip) | |||||
def wait_for_block(node, block, status="invalid"): | |||||
def check_block(): | def check_block(): | ||||
for tip in node.getchaintips(): | for tip in node.getchaintips(): | ||||
if tip["hash"] == block: | if tip["hash"] == block: | ||||
assert(tip["status"] != "active") | assert(tip["status"] != "active") | ||||
return tip["status"] == "invalid" | return tip["status"] == status | ||||
return False | return False | ||||
wait_until(check_block) | wait_until(check_block) | ||||
# node do not accept alt_node's chain due to tip being finalized, | # First block header is accepted as valid-header | ||||
# even though it is longer. | alt_node.generate(1) | ||||
wait_for_invalid_block(node, alt_node.getbestblockhash()) | alt_210 = alt_node.getbestblockhash() | ||||
assert_equal(node.getbestblockhash(), tip) | wait_for_block(node, alt_node.getbestblockhash(), "valid-headers") | ||||
# alt_node has mined 10 block, so tip must be invalid due to finalization. | # Second block header is accepted but set invalid | ||||
wait_for_invalid_block(alt_node, tip) | alt_node.generate(1) | ||||
invalid_block = alt_node.getbestblockhash() | |||||
wait_for_block(node, invalid_block) | |||||
# Later block headers are rejected | |||||
for i in range(2, 9): | |||||
alt_node.generate(1) | |||||
assert_raises_rpc_error(-5, RPC_BLOCK_NOT_FOUND_ERROR, | |||||
node.getblockheader, alt_node.getbestblockhash()) | |||||
self.log.info("Test that an invalid block cannot be finalized...") | self.log.info("Test that an invalid block cannot be finalized...") | ||||
assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, | assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, | ||||
node.finalizeblock, alt_node.getbestblockhash()) | node.finalizeblock, invalid_block) | ||||
self.log.info( | self.log.info( | ||||
"Test that invalidating a finalized block moves the finalization backward...") | "Test that invalidating a finalized block moves the finalization backward...") | ||||
# Node's finalized block will be invalidated, which causes the finalized block to | |||||
# move to the previous block. | |||||
# | |||||
# Expected state: | |||||
# | |||||
# On alt_node: | |||||
# >(210)->(211)-> // ->(218 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(208 auto-finalized)->(209)->(210 invalid) | |||||
# | |||||
# On node: | |||||
# >(210 valid-headers)->(211 invalid)->(212 to 218 dropped) | |||||
# / | |||||
# (200 finalized)->(201)-> // ->(209)->(210 tip) | |||||
node.invalidateblock(tip) | node.invalidateblock(tip) | ||||
node.reconsiderblock(tip) | node.reconsiderblock(tip) | ||||
finalized_block = node.getblockhash( | finalized_block = node.getblockhash( | ||||
int(node.getblockheader(tip)['height']) - AUTO_FINALIZATION_DEPTH) | int(node.getblockheader(tip)['height']) - AUTO_FINALIZATION_DEPTH) | ||||
assert_equal(node.getbestblockhash(), tip) | assert_equal(node.getbestblockhash(), tip) | ||||
assert_equal( | assert_equal( | ||||
node.getfinalizedblockhash(), | node.getfinalizedblockhash(), | ||||
finalized_block) | finalized_block) | ||||
# The node will now accept that chain as the finalized block moved back. | # The node will now accept that chain as the finalized block moved back. | ||||
node.reconsiderblock(alt_node.getbestblockhash()) | # Generate a new block on alt_node to trigger getheader from node | ||||
# Previous 212-218 height blocks have been droped because their previous was invalid | |||||
# | |||||
# Expected state: | |||||
# | |||||
# On alt_node: | |||||
# >(210)->(211)-> // ->(218)->(219 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(209 auto-finalized)->(210 invalid) | |||||
# | |||||
# On node: | |||||
# >(210)->(211)->(212)-> // ->(218)->(219 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(209 finalized)->(210) | |||||
node.reconsiderblock(invalid_block) | |||||
alt_node.generate(1) | |||||
sync_blocks(self.nodes[0:2]) | |||||
assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) | assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) | ||||
assert_equal(node.getfinalizedblockhash(), auto_finalized_tip) | assert_equal(node.getfinalizedblockhash(), auto_finalized_tip) | ||||
self.log.info("Trigger reorg via block finalization...") | self.log.info("Trigger reorg via block finalization...") | ||||
# Finalize node tip to reorg | |||||
# | |||||
# Expected state: | |||||
# | |||||
# On alt_node: | |||||
# >(210)->(211)-> // ->(218)->(219 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(209 auto-finalized)->(210 invalid) | |||||
# | |||||
# On node: | |||||
# >(210 invalid)-> // ->(219 invalid) | |||||
# / | |||||
# (200 finalized)->(201)-> // ->(209)->(210 tip) | |||||
node.finalizeblock(tip) | node.finalizeblock(tip) | ||||
assert_equal(node.getfinalizedblockhash(), | assert_equal(node.getfinalizedblockhash(), | ||||
finalized_block) | finalized_block) | ||||
self.log.info("Try to finalized a block on a competiting fork...") | self.log.info("Try to finalize a block on a competiting fork...") | ||||
assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, | assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, | ||||
node.finalizeblock, alt_node.getbestblockhash()) | node.finalizeblock, alt_node.getbestblockhash()) | ||||
assert node.getfinalizedblockhash() != alt_node.getbestblockhash(), \ | assert node.getfinalizedblockhash() != alt_node.getbestblockhash(), \ | ||||
"Finalize block is alt_node's tip!" | "Finalized block should not be alt_node's tip!" | ||||
assert_equal(node.getfinalizedblockhash(), finalized_block) | assert_equal(node.getfinalizedblockhash(), finalized_block) | ||||
self.log.info( | self.log.info( | ||||
"Make sure reconsidering block move the finalization point...") | "Make sure reconsidering block move the finalization point...") | ||||
# Reconsidering alt_node tip will move finalized block on node | |||||
# | |||||
# Expected state: | |||||
# | |||||
# On alt_node: | |||||
# >(210)->(211)-> // ->(218)->(219 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(209 auto-finalized)->(210 invalid) | |||||
# | |||||
# On node: | |||||
# >(210)-> // ->(219 tip) | |||||
# / | |||||
# (200)->(201)-> // ->(209 auto-finalized)->(210) | |||||
node.reconsiderblock(alt_node.getbestblockhash()) | node.reconsiderblock(alt_node.getbestblockhash()) | ||||
assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) | assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
FinalizeBlockTest().main() | FinalizeBlockTest().main() |