diff --git a/test/functional/abc-finalize-block.py b/test/functional/abc-finalize-block.py --- a/test/functional/abc-finalize-block.py +++ b/test/functional/abc-finalize-block.py @@ -9,6 +9,8 @@ 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_FORK_PRIOR_FINALIZED_ERROR = 'bad-fork-prior-finalized' +RPC_BLOCK_NOT_FOUND_ERROR = 'Block not found' AUTO_FINALIZATION_DEPTH = 10 @@ -36,6 +38,7 @@ tip = node.getbestblockhash() node.finalizeblock(tip) assert_equal(node.getbestblockhash(), tip) + assert_equal(node.getfinalizedblockhash(), tip) alt_node = self.nodes[1] connect_nodes_bi(self.nodes, 0, 1) @@ -44,63 +47,194 @@ alt_node.invalidateblock(tip) # We will use this later to check auto-finalization during a reorg auto_finalized_tip = alt_node.getbestblockhash() - alt_node.generate(10) - # Wait for node 0 to invalidate the chain. - def wait_for_invalid_block(node, block): + # Node 0 should not accept the whole alt_node's chain due to tip being finalized, + # 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, it will be accepted but inactive + # - 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(): for tip in node.getchaintips(): if tip["hash"] == block: assert(tip["status"] != "active") - return tip["status"] == "invalid" + return tip["status"] == status return False wait_until(check_block) - # node do not accept alt_node's chain due to tip being finalized, - # even though it is longer. - wait_for_invalid_block(node, alt_node.getbestblockhash()) - assert_equal(node.getbestblockhash(), tip) + # First block header is accepted as valid-header + alt_node.generate(1) + alt_210 = alt_node.getbestblockhash() + wait_for_block(node, alt_node.getbestblockhash(), "valid-headers") - # alt_node has mined 10 block, so tip must be invalid due to finalization. - wait_for_invalid_block(alt_node, tip) + # Second block header is accepted but set invalid + 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()) + + assert_equal(node.getbestblockhash(), tip) + assert_equal(node.getfinalizedblockhash(), tip) self.log.info("Test that an invalid block cannot be finalized...") assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, - node.finalizeblock, alt_node.getbestblockhash()) + node.finalizeblock, invalid_block) self.log.info( "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)->(201)-> // ->(209 finalized)->(210 tip) node.invalidateblock(tip) node.reconsiderblock(tip) - finalized_block = node.getblockhash( - int(node.getblockheader(tip)['height']) - AUTO_FINALIZATION_DEPTH) + assert_equal(node.getbestblockhash(), tip) - assert_equal( - node.getfinalizedblockhash(), - finalized_block) + + assert_equal(node.getblockheader( + node.getbestblockhash())['height'], 210) + assert_equal(node.getblockheader( + node.getfinalizedblockhash())['height'], 209) + + assert_equal(alt_node.getblockheader( + alt_node.getbestblockhash())['height'], 218) + assert_equal(alt_node.getblockheader( + alt_node.getfinalizedblockhash())['height'], 208) # 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.getfinalizedblockhash(), auto_finalized_tip) + assert_equal(node.getblockheader( + node.getbestblockhash())['height'], 219) + assert_equal(node.getblockheader( + node.getfinalizedblockhash())['height'], 209) + + assert_equal(alt_node.getblockheader( + alt_node.getbestblockhash())['height'], 219) + assert_equal(alt_node.getblockheader( + alt_node.getfinalizedblockhash())['height'], 209) + self.log.info("Trigger reorg via block finalization...") - node.finalizeblock(tip) - assert_equal(node.getfinalizedblockhash(), - finalized_block) + # Generate some more blockes on alt_node. Auto-finalization will occur on + # both chains. + # + # Expected state: + # + # On alt_node: + # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) + # / + # (200)->(201)-> // ->(209)->(210 invalid) + # + # On node: + # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) + # / + # (200)->(201)-> // ->(209)->(210 invalid) + for i in range(10): + alt_node.generate(1) + sync_blocks(self.nodes[0:2]) - self.log.info("Try to finalized a block on a competiting fork...") - assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, - node.finalizeblock, alt_node.getbestblockhash()) - assert node.getfinalizedblockhash() != alt_node.getbestblockhash(), \ - "Finalize block is alt_node's tip!" - assert_equal(node.getfinalizedblockhash(), finalized_block) + assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) + + assert_equal(node.getblockheader( + node.getbestblockhash())['height'], 229) + assert_equal(node.getblockheader( + node.getfinalizedblockhash())['height'], 219) + + assert_equal(alt_node.getblockheader( + alt_node.getbestblockhash())['height'], 229) + assert_equal(alt_node.getblockheader( + alt_node.getfinalizedblockhash())['height'], 219) + + self.log.info("Try to finalize a block on a competiting fork...") + # Finalizing the tip should not be allowed, because it would + # fork before a finalized block + assert_raises_rpc_error(-20, RPC_FORK_PRIOR_FINALIZED_ERROR, + node.finalizeblock, tip) + assert node.getfinalizedblockhash() != tip, \ + "Finalization should not occur: a competing chain is already finalized" + + self.log.info( + "Try to finalize a block on an already finalized chain...") + # Finalizing a block of an already finalized chain should have no effect + block_218 = node.getblockhash(218) + node.finalizeblock(block_218) + assert_equal(node.getblockheader( + node.getfinalizedblockhash())['height'], 219) self.log.info( "Make sure reconsidering block move the finalization point...") - node.reconsiderblock(alt_node.getbestblockhash()) + # Reconsidering the tip will move back the finalized block on node + # + # Expected state: + # + # On alt_node: + # >(210)->(211)-> // ->(219 auto-finalized)-> // ->(229 tip) + # / + # (200)->(201)-> // ->(209)->(210 invalid) + # + # On node: + # >(210)->(211)-> // ->(219)-> // ->(229 tip) + # / + # (200)->(201)-> // ->(209 finalized)->(210) + node.reconsiderblock(tip) + assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) + assert_equal(node.getblockheader( + node.getbestblockhash())['height'], 229) + assert_equal(node.getblockheader( + node.getfinalizedblockhash())['height'], 209) + if __name__ == '__main__': FinalizeBlockTest().main()