예제 #1
0
class TestChainController(unittest.TestCase):
    def setUp(self):
        self.blocks = BlockTreeManager()
        self.gossip = MockNetwork()
        self.executor = SynchronousExecutor()
        self.txn_executor = MockTransactionExecutor()
        self.block_sender = MockBlockSender()
        self.state_view_factory = MockStateViewFactory()

        def chain_updated(head, committed_batches=None,
                          uncommitted_batches=None):
            pass

        self.chain_ctrl = ChainController(
            consensus_module=mock_consensus,
            block_cache=self.blocks.block_cache,
            state_view_factory=self.state_view_factory,
            block_sender=self.block_sender,
            executor=self.executor,
            transaction_executor=MockTransactionExecutor(),
            on_chain_updated=chain_updated,
            squash_handler=None)

    def test_simple_case(self):
        # TEST Run the simple case
        block_1 = self.blocks.generate_block(self.blocks.chain_head)
        self.chain_ctrl.on_block_received(block_1)
        self.executor.process_all()
        assert(self.chain_ctrl.chain_head.block.header_signature ==
               block_1.header_signature)

    def test_alternate_genesis(self):
        # TEST Run generate and alternate genesis block
        head = self.chain_ctrl.chain_head
        for b in self.blocks.generate_chain(None, 5,
                                            {"add_to_cache": True}):
            self.chain_ctrl.on_block_received(b)
            self.executor.process_all()
        assert(self.chain_ctrl.chain_head.block.header_signature ==
               head.block.header_signature)

    def test_bad_block_signature(self):
        # TEST Bad block extending current chain
        # Bad due to signature
        head = self.blocks.chain_head
        block_bad = self.blocks.generate_block(self.blocks.chain_head.block,
                                               invalid_signature=True)
        self.chain_ctrl.on_block_received(block_bad)
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                head.block.header_signature)

    def test_bad_block_consensus(self):
        # Bad due to consensus
        pass

    def test_bad_block_transaction(self):
        # Bad due to transaction
        pass
예제 #2
0
class TestChainController(unittest.TestCase):
    def setUp(self):
        self.blocks = BlockTreeManager()
        self.gossip = MockNetwork()
        self.executor = SynchronousExecutor()
        self.txn_executor = MockTransactionExecutor()
        self.block_sender = MockBlockSender()

        def chain_updated(head):
            pass

        self.chain_ctrl = ChainController(
            consensus=TestModeVerifier(),
            block_cache=self.blocks.block_cache,
            block_sender=self.block_sender,
            executor=self.executor,
            transaction_executor=MockTransactionExecutor(),
            on_chain_updated=chain_updated,
            squash_handler=None)

    def test_simple_case(self):
        # TEST Run the simple case
        block_1 = self.blocks.generate_block(self.blocks.chain_head)
        self.chain_ctrl.on_block_received(block_1)
        self.executor.process_all()
        assert(self.chain_ctrl.chain_head.block.header_signature ==
               block_1.header_signature)

    def test_alternate_genesis(self):
        # TEST Run generate and alternate genesis block
        head = self.chain_ctrl.chain_head
        other_genesis = self.blocks.generate_block(add_to_store=True)
        for b in self.blocks.generate_chain(other_genesis, 5):
            self.chain_ctrl.on_block_received(b)
            self.executor.process_all()
        assert(self.chain_ctrl.chain_head.block.header_signature ==
               head.block.header_signature)

    def test_bad_block_signature(self):
        # TEST Bad block extending current chain
        # Bad due to signature
        head = self.blocks.chain_head
        block_bad = self.blocks.generate_block(self.blocks.chain_head.block,
                                               invalid_signature=True)
        self.chain_ctrl.on_block_received(block_bad)
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                head.block.header_signature)

    def test_bad_block_consensus(self):
        # Bad due to consensus
        pass

    def test_bad_block_transaction(self):
        # Bad due to transaction
        pass
예제 #3
0
class TestChainController(unittest.TestCase):
    def setUp(self):
        self.blocks = BlockTreeManager()
        self.gossip = MockNetwork()
        self.executor = SynchronousExecutor()
        self.txn_executor = MockTransactionExecutor()
        self.block_sender = MockBlockSender()

        def chain_updated(head):
            pass

        self.chain_ctrl = ChainController(
            consensus=TestModeVerifier(),
            block_store=self.blocks.block_store,
            block_sender=self.block_sender,
            executor=self.executor,
            transaction_executor=MockTransactionExecutor(),
            on_chain_updated=chain_updated,
            squash_handler=None)

    def test_simple_case(self):
        # TEST Run the simple case
        block_1 = self.blocks.generate_block(self.blocks.chain_head)
        self.chain_ctrl.on_block_received(block_1.get_block())
        self.executor.process_all()
        assert(self.chain_ctrl.chain_head.block.header_signature ==
               block_1.header_signature)

    def test_alternate_genesis(self):
        # TEST Run generate and alternate genesis block
        head = self.chain_ctrl.chain_head
        other_genesis = self.blocks.generate_block(add_to_store=True)
        for b in self.blocks.generate_chain(other_genesis, 5):
            self.chain_ctrl.on_block_received(b.get_block())
            self.executor.process_all()
        assert(self.chain_ctrl.chain_head.block.header_signature ==
               head.block.header_signature)

    def test_bad_block_signature(self):
        # TEST Bad block extending current chain
        # Bad due to signature
        head = self.blocks.chain_head
        block_bad = self.blocks.generate_block(self.blocks.chain_head.block,
                                               invalid_signature=True)
        self.chain_ctrl.on_block_received(block_bad.get_block())
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                head.block.header_signature)

    def test_bad_block_consensus(self):
        # Bad due to consensus
        pass

    def test_bad_block_transaction(self):
        # Bad due to transaction
        pass
예제 #4
0
    def test_is_valid_subscription_known_old_chain(self):
        """Test that a check for a valid subscription where the known block id
        is a block in the middle of the chain should return True.
        """
        mock_service = Mock()
        block_tree_manager = BlockTreeManager()

        chain = block_tree_manager.generate_chain(
            block_tree_manager.chain_head, 5)

        # Grab an id from the middle of the chain
        known_id = chain[3].identifier
        block_tree_manager.block_store.update_chain(chain)

        delta_store = StateDeltaStore(DictDatabase())
        delta_processor = StateDeltaProcessor(
            service=mock_service,
            state_delta_store=delta_store,
            block_store=block_tree_manager.block_store)

        self.assertTrue(delta_processor.is_valid_subscription([known_id]))
예제 #5
0
class TestChainController(unittest.TestCase):
    def setUp(self):
        self.block_tree_manager = BlockTreeManager()
        self.gossip = MockNetwork()
        self.executor = SynchronousExecutor()
        self.txn_executor = MockTransactionExecutor()
        self.block_sender = MockBlockSender()
        self.chain_id_manager = MockChainIdManager()
        self._chain_head_lock = RLock()
        self.state_delta_processor = MockStateDeltaProcessor()

        def chain_updated(head, committed_batches=None,
                          uncommitted_batches=None):
            pass

        self.chain_ctrl = ChainController(
            block_cache=self.block_tree_manager.block_cache,
            state_view_factory=MockStateViewFactory(
                self.block_tree_manager.state_db),
            block_sender=self.block_sender,
            executor=self.executor,
            transaction_executor=MockTransactionExecutor(),
            chain_head_lock=self._chain_head_lock,
            on_chain_updated=chain_updated,
            squash_handler=None,
            chain_id_manager=self.chain_id_manager,
            state_delta_processor=self.state_delta_processor,
            identity_signing_key=self.block_tree_manager.identity_signing_key,
            data_dir=None,
            config_dir=None)

        init_root = self.chain_ctrl.chain_head
        self.assert_is_chain_head(init_root)

        # create a chain of length 5 extending the root
        _, head = self.generate_chain(init_root, 5)
        self.receive_and_process_blocks(head)
        self.assert_is_chain_head(head)

        self.init_head = head

    def test_simple_case(self):
        new_block = self.generate_block(self.init_head)
        self.receive_and_process_blocks(new_block)
        self.assert_is_chain_head(new_block)
        # validate that the deltas for the new block are published
        self.assertEqual(new_block, self.state_delta_processor.block)

    def test_alternate_genesis(self):
        '''Tests a fork extending an alternate genesis block
        '''
        chain, head = self.generate_chain(None, 5)

        for block in chain:
            self.receive_and_process_blocks(block)

        # make sure initial head is still chain head
        self.assert_is_chain_head(self.init_head)

    def test_bad_blocks(self):
        '''Tests bad blocks extending current chain
        '''
        # Bad due to consensus
        bad_consen = self.generate_block(
            previous_block=self.init_head,
            invalid_consensus=True)

        # chain head should be the same
        self.receive_and_process_blocks(bad_consen)
        self.assert_is_chain_head(self.init_head)

        # Bad due to transaction
        bad_batch = self.generate_block(
            previous_block=self.init_head,
            invalid_batch=True)

        # chain head should be the same
        self.receive_and_process_blocks(bad_batch)
        self.assert_is_chain_head(self.init_head)

        # # Ensure good block works
        good_block = self.generate_block(
            previous_block=self.init_head)

        # chain head should be good_block
        self.receive_and_process_blocks(good_block)
        self.assert_is_chain_head(good_block)

    def test_fork_weights(self):
        '''Tests extending blocks of different weights
        '''
        weight_4 = self.generate_block(
            previous_block=self.init_head,
            weight=4)

        weight_7 = self.generate_block(
            previous_block=self.init_head,
            weight=7)

        weight_8 = self.generate_block(
            previous_block=self.init_head,
            weight=8)

        self.receive_and_process_blocks(
            weight_7,
            weight_4,
            weight_8)

        self.assert_is_chain_head(weight_8)

    def test_fork_lengths(self):
        '''Tests competing forks of different lengths
        '''
        _, head_2 = self.generate_chain(self.init_head, 2)
        _, head_7 = self.generate_chain(self.init_head, 7)
        _, head_5 = self.generate_chain(self.init_head, 5)

        self.receive_and_process_blocks(
            head_2,
            head_7,
            head_5)

        self.assert_is_chain_head(head_7)

    def test_advancing_chain(self):
        '''Tests the chain being advanced between a fork's
        creation and validation
        '''
        _, fork_5 = self.generate_chain(self.init_head, 5)
        _, fork_3 = self.generate_chain(self.init_head, 3)

        self.receive_and_process_blocks(fork_3)
        self.assert_is_chain_head(fork_3)

        # fork_5 is longer than fork_3, so it should be accepted
        self.receive_and_process_blocks(fork_5)
        self.assert_is_chain_head(fork_5)

    def test_fork_missing_block(self):
        '''Tests a fork with a missing block
        '''
        # make new chain
        new_chain, new_head = self.generate_chain(self.init_head, 5)

        self.chain_ctrl.on_block_received(new_head)

        # delete a block from the new chain
        del self.chain_ctrl._block_cache[new_chain[3].identifier]

        self.executor.process_all()

        # chain shouldn't advance
        self.assert_is_chain_head(self.init_head)

        # try again, chain still shouldn't advance
        self.receive_and_process_blocks(new_head)

        self.assert_is_chain_head(self.init_head)

    def test_fork_bad_block(self):
        '''Tests a fork with a bad block in the middle
        '''
        # make two chains extending chain
        good_chain, good_head = self.generate_chain(self.init_head, 5)
        bad_chain, bad_head = self.generate_chain(self.init_head, 5)

        self.chain_ctrl.on_block_received(bad_head)
        self.chain_ctrl.on_block_received(good_head)

        # invalidate block in the middle of bad_chain
        bad_chain[3].status = BlockStatus.Invalid

        self.executor.process_all()

        # good_chain should be accepted
        self.assert_is_chain_head(good_head)

    def test_advancing_fork(self):
        '''Tests a fork advancing before getting validated
        '''
        _, fork_head = self.generate_chain(self.init_head, 5)

        self.chain_ctrl.on_block_received(fork_head)

        # advance fork before it gets accepted
        _, ext_head = self.generate_chain(fork_head, 3)

        self.executor.process_all()

        self.assert_is_chain_head(fork_head)

        self.receive_and_process_blocks(ext_head)

        self.assert_is_chain_head(ext_head)

    def test_block_extends_in_validation(self):
        '''Tests a block getting extended while being validated
        '''
        # create candidate block
        candidate = self.block_tree_manager.generate_block(
            previous_block=self.init_head)

        self.assert_is_chain_head(self.init_head)

        # queue up the candidate block, but don't process
        self.chain_ctrl.on_block_received(candidate)

        # create a new block extending the candidate block
        extending_block = self.block_tree_manager.generate_block(
            previous_block=candidate)

        self.assert_is_chain_head(self.init_head)

        # queue and process the extending block,
        # which should be the new head
        self.receive_and_process_blocks(extending_block)
        self.assert_is_chain_head(extending_block)

    def test_multiple_extended_forks(self):
        '''A more involved example of competing forks

        Three forks of varying lengths a_0, b_0, and c_0
        are created extending the existing chain, with c_0
        being the longest initially. The chains are extended
        in the following sequence:

        1. Extend all forks by 2. The c fork should remain the head.
        2. Extend forks by lenths such that the b fork is the
           longest. It should be the new head.
        3. Extend all forks by 8. The b fork should remain the head.
        4. Create a new fork of the initial chain longer than
           any of the other forks. It should be the new head.
        '''

        # create forks of various lengths
        _, a_0 = self.generate_chain(self.init_head, 3)
        _, b_0 = self.generate_chain(self.init_head, 5)
        _, c_0 = self.generate_chain(self.init_head, 7)

        self.receive_and_process_blocks(a_0, b_0, c_0)
        self.assert_is_chain_head(c_0)

        # extend every fork by 2
        _, a_1 = self.generate_chain(a_0, 2)
        _, b_1 = self.generate_chain(b_0, 2)
        _, c_1 = self.generate_chain(c_0, 2)

        self.receive_and_process_blocks(a_1, b_1, c_1)
        self.assert_is_chain_head(c_1)

        # extend the forks by different lengths
        _, a_2 = self.generate_chain(a_1, 1)
        _, b_2 = self.generate_chain(b_1, 6)
        _, c_2 = self.generate_chain(c_1, 3)

        self.receive_and_process_blocks(a_2, b_2, c_2)
        self.assert_is_chain_head(b_2)

        # extend every fork by 2
        _, a_3 = self.generate_chain(a_2, 8)
        _, b_3 = self.generate_chain(b_2, 8)
        _, c_3 = self.generate_chain(c_2, 8)

        self.receive_and_process_blocks(a_3, b_3, c_3)
        self.assert_is_chain_head(b_3)

        # create a new longest chain
        _, wow = self.generate_chain(self.init_head, 30)
        self.receive_and_process_blocks(wow)
        self.assert_is_chain_head(wow)

    # next multi threaded
    # next add block publisher
    # next batch lists
    # integrate with LMDB
    # early vs late binding ( class member of consensus BlockPublisher)

    # helpers

    def assert_is_chain_head(self, block):
        chain_head_sig = self.chain_ctrl.chain_head.header_signature
        block_sig = block.header_signature

        self.assertEqual(
            chain_head_sig[:8],
            block_sig[:8],
            'Not chain head')

    def generate_chain(self, root_block, num_blocks,
                                 params={'add_to_cache': True}):
        '''Returns (chain, chain_head).
        Usually only the head is needed,
        but occasionally the chain itself is used.
        '''
        chain = self.block_tree_manager.generate_chain(
            root_block, num_blocks, params)

        head = chain[-1]

        return chain, head

    def generate_block(self, *args, **kwargs):
        return self.block_tree_manager.generate_block(
            *args, **kwargs)

    def receive_and_process_blocks(self, *blocks):
        for block in blocks:
            self.chain_ctrl.on_block_received(block)
        self.executor.process_all()
예제 #6
0
class TestBlockValidator(unittest.TestCase):
    def setUp(self):
        self.state_view_factory = MockStateViewFactory()

        self.block_tree_manager = BlockTreeManager()
        self.root = self.block_tree_manager.chain_head

        self.block_validation_handler = self.BlockValidationHandler()

    # fork based tests
    def test_fork_simple(self):
        """
        Test a simple case of a new block extending the current root.
        """

        new_block = self.block_tree_manager.generate_block(
            previous_block=self.root,
            add_to_store=True)

        self.validate_block(new_block)

        self.assert_valid_block(new_block)
        self.assert_new_block_committed()

    def test_good_fork_lower(self):
        """
        Test case of a new block extending on a valid chain but not as long
        as the current chain.
        """
        # create a new valid chain 5 long from the current root
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        self.block_tree_manager.set_chain_head(head)

        # generate candidate chain 3 long from the same root
        new_chain, new_head = self.generate_chain_with_head(
            self.root, 3, {'add_to_cache': True})

        self.validate_block(new_head)

        self.assert_valid_block(new_head)
        self.assert_new_block_not_committed()

    def test_good_fork_higher(self):
        """
        Test case of a new block extending on a valid chain but longer
        than the current chain. ( similar to test_good_fork_lower but uses
        a different code path when finding the common root )
        """
        # create a new valid chain 5 long from the current root
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        self.block_tree_manager.set_chain_head(head)

        # generate candidate chain 8 long from the same root
        new_chain, new_head = self.generate_chain_with_head(
            head, 8, {'add_to_cache': True})

        self.validate_block(new_head)

        self.assert_valid_block(new_head)
        self.assert_new_block_committed()

    def test_fork_different_genesis(self):
        """"
        Test the case where new block is from a different genesis
        """
        # create a new valid chain 5 long from the current root
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        self.block_tree_manager.set_chain_head(head)

        # generate candidate chain 5 long from its own genesis
        new_chain, new_head = self.generate_chain_with_head(
            None, 5, {'add_to_cache': True})

        self.validate_block(new_head)

        self.assert_invalid_block(new_head)
        self.assert_new_block_not_committed()

    def test_fork_missing_predecessor(self):
        """"
        Test the case where new block is missing the a predecessor
        """
        # generate candidate chain 5 long off the current head.
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_cache': True})

        # remove one of the new blocks
        del self.block_tree_manager.block_cache[chain[1].identifier]

        self.validate_block(head)

        self.assert_invalid_block(head)
        self.assert_new_block_not_committed()

    def test_fork_invalid_predecessor(self):
        """"
        Test the case where new block has an invalid predecessor
        """
        # generate candidate chain 5 long off the current head.
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_cache': True})

        # Mark a predecessor as invalid
        chain[1].status = BlockStatus.Invalid

        self.validate_block(head)

        self.assert_invalid_block(head)
        self.assert_new_block_not_committed()

    def test_block_bad_consensus(self):
        """
        Test the case where the new block has a bad batch
        """
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        new_block = self.block_tree_manager.generate_block(
            previous_block=head,
            add_to_cache=True,
            invalid_consensus=True)

        self.validate_block(new_block)

        self.assert_invalid_block(new_block)
        self.assert_new_block_not_committed()

    def test_block_bad_batch(self):
        """
        Test the case where the new block has a bad batch
        """
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        new_block = self.block_tree_manager.generate_block(
            previous_block=head,
            add_to_cache=True,
            invalid_batch=True)

        self.validate_block(new_block)

        self.assert_invalid_block(new_block)
        self.assert_new_block_not_committed()

    def test_block_missing_batch(self):
        """
        Test the case where the new block is missing a batch.
        """
        pass

    def test_block_extra_batch(self):
        """
        Test the case where the new block has an extra batch.
        """
        pass

    def test_block_batches_order(self):
        """
        Test the case where the new block has batches that are
        out of order.
        """
        pass

    def test_block_missing_batch_dependency(self):
        """
        Test the case where the new block has a batch that is missing a
        dependency.
        """
        pass

    # assertions

    def assert_valid_block(self, block):
        self.assertEqual(
            block.status, BlockStatus.Valid,
            "Block should be valid")

    def assert_invalid_block(self, block):
        self.assertEqual(
            block.status, BlockStatus.Invalid,
            "Block should be invalid")

    def assert_new_block_committed(self):
        self.assert_handler_has_result()
        self.assertTrue(
            self.block_validation_handler.result["commit_new_block"],
            "New block not committed, should be")

    def assert_new_block_not_committed(self):
        self.assert_handler_has_result()
        self.assertFalse(
            self.block_validation_handler.result["commit_new_block"],
            "New block committed, shouldn't be")

    def assert_handler_has_result(self):
        msg = "Validation handler doesn't have result"
        self.assertTrue(self.block_validation_handler.has_result(), msg)

    # block validation

    def validate_block(self, block):
        validator = self.create_block_validator(
            block,
            self.block_validation_handler.on_block_validated)

        validator.run()

    def create_block_validator(self, new_block, on_block_validated):
        return BlockValidator(
            consensus_module=mock_consensus,
            new_block=new_block,
            chain_head=self.block_tree_manager.chain_head,
            state_view_factory=self.state_view_factory,
            block_cache=self.block_tree_manager.block_cache,
            done_cb=on_block_validated,
            executor=MockTransactionExecutor(),
            squash_handler=None,
            identity_signing_key=self.block_tree_manager.identity_signing_key,
            data_dir=None,
            config_dir=None)

    class BlockValidationHandler(object):
        def __init__(self):
            self.result = None

        def on_block_validated(self, commit_new_block, result):
            result["commit_new_block"] = commit_new_block
            self.result = result

        def has_result(self):
            return self.result is not None

    # block tree manager interface

    def generate_chain_with_head(self, root_block, num_blocks, params=None):
        chain = self.block_tree_manager.generate_chain(
            root_block, num_blocks, params)

        head = chain[-1]

        return chain, head
class TestBlockValidator():
    def __init__(self):
        self.state_view_factory = MockStateViewFactory()

        self.block_tree_manager = BlockTreeManager()
        self.root = self.block_tree_manager.chain_head

        self.block_validation_handler = self.BlockValidationHandler()
        self.permission_verifier = MockPermissionVerifier()

    # assertions

    def assert_valid_block(self, block):
        assert block.status == BlockStatus.Valid

    def assert_invalid_block(self, block):
        assert block.status == BlockStatus.Invalid

    def assert_unknown_block(self, block):
        assert block.status == BlockStatus.Unknown

    def assert_new_block_committed(self):
        self.assert_handler_has_result()
        assert self.block_validation_handler.commit_new_block == True

    def assert_new_block_not_committed(self):
        self.assert_handler_has_result()
        assert self.block_validation_handler.commit_new_block == False

    def assert_handler_has_result(self):
        msg = "Validation handler doesn't have result"
        assert self.block_validation_handler.has_result() == True

    # block validation

    def validate_block(self, block):
        validator = self.create_block_validator()
        validator._load_consensus = lambda block: mock_consensus
        validator.process_block_verification(
            block, self.block_validation_handler.on_block_validated)

    def create_block_validator(self):
        return BlockValidator(
            state_view_factory=self.state_view_factory,
            block_cache=self.block_tree_manager.block_cache,
            transaction_executor=MockTransactionExecutor(
                batch_execution_result=None),
            squash_handler=None,
            identity_signer=self.block_tree_manager.identity_signer,
            data_dir=None,
            config_dir=None,
            permission_verifier=self.permission_verifier)

    class BlockValidationHandler(object):
        def __init__(self):
            self.commit_new_block = None
            self.result = None

        def on_block_validated(self, commit_new_block, result):
            self.commit_new_block = commit_new_block
            self.result = result

        def has_result(self):
            return not (self.result is None or self.commit_new_block is None)

    # block tree manager interface

    def generate_chain_with_head(self,
                                 root_block,
                                 num_blocks,
                                 params=None,
                                 exclude_head=True):
        chain = self.block_tree_manager.generate_chain(root_block, num_blocks,
                                                       params, exclude_head)

        head = chain[-1]

        return chain, head
class TestChainController():
    def __init__(self):
        self.block_tree_manager = BlockTreeManager()
        self.gossip = MockNetwork()
        self.txn_executor = MockTransactionExecutor()
        self.block_sender = MockBlockSender()
        self.chain_id_manager = MockChainIdManager()
        self._chain_head_lock = RLock()
        self.permission_verifier = MockPermissionVerifier()
        self.state_view_factory = MockStateViewFactory(
            self.block_tree_manager.state_db)
        self.transaction_executor = MockTransactionExecutor(
            batch_execution_result=None)
        self.executor = SynchronousExecutor()

        self.block_validator = BlockValidator(
            state_view_factory=self.state_view_factory,
            block_cache=self.block_tree_manager.block_cache,
            transaction_executor=self.transaction_executor,
            squash_handler=None,
            identity_signer=self.block_tree_manager.identity_signer,
            data_dir=None,
            config_dir=None,
            permission_verifier=self.permission_verifier,
            thread_pool=self.executor)

        def chain_updated(head,
                          committed_batches=None,
                          uncommitted_batches=None):
            pass

        self.chain_ctrl = ChainController(
            block_cache=self.block_tree_manager.block_cache,
            block_validator=self.block_validator,
            state_view_factory=self.state_view_factory,
            chain_head_lock=self._chain_head_lock,
            on_chain_updated=chain_updated,
            chain_id_manager=self.chain_id_manager,
            data_dir=None,
            config_dir=None,
            chain_observers=[])

        init_root = self.chain_ctrl.chain_head
        self.assert_is_chain_head(init_root)

        # create a chain of length 5 extending the root
        _, head = self.generate_chain(init_root, 5)
        self.receive_and_process_blocks(head)
        self.assert_is_chain_head(head)

        self.init_head = head

    # next multi threaded
    # next add block publisher
    # next batch lists
    # integrate with LMDB
    # early vs late binding ( class member of consensus BlockPublisher)

    # helpers

    def assert_is_chain_head(self, block):
        chain_head_sig = self.chain_ctrl.chain_head.header_signature
        block_sig = block.header_signature

        # assert chain_head_sig[:8] == block_sig[:8]

    def generate_chain(self, root_block, num_blocks, params=None):
        '''Returns (chain, chain_head).
        Usually only the head is needed,
        but occasionally the chain itself is used.
        '''
        if params is None:
            params = {'add_to_cache': True}

        chain = self.block_tree_manager.generate_chain(root_block, num_blocks,
                                                       params)

        head = chain[-1]

        return chain, head

    def generate_block(self, *args, **kwargs):
        return self.block_tree_manager.generate_block(*args, **kwargs)

    def receive_and_process_blocks(self, *blocks):
        for block in blocks:
            self.chain_ctrl.on_block_received(block)
        self.executor.process_all()
예제 #9
0
class TestChainCommitState(unittest.TestCase):
    def setUp(self):
        self.commit_state = None
        self.block_tree_manager = BlockTreeManager()

    def test_fall_thru(self):
        """ Test that the requests correctly fall thru to the
        underlying BlockStore.
        """
        blocks = self.generate_chain(1)
        commit_state = self.create_chain_commit_state(blocks)
        self.commit_state = commit_state

        self.assert_block_present(blocks[0])

        self.assert_missing()

    def test_uncommitted_blocks(self):
        """ Test that the ChainCommitState can simulate blocks being
        uncommited from the BlockStore
        """
        blocks = self.generate_chain(2)
        block, uncommitted_block = blocks
        commit_state = self.create_chain_commit_state(
            blocks=blocks,
            uncommitted_blocks=[uncommitted_block],
        )
        self.commit_state = commit_state

        # the first block is still present
        self.assert_block_present(block)
        # batch from the uncommited block should not be present
        self.assert_block_not_present(uncommitted_block)

        self.assert_missing()

    def test_add_remove_batch(self):
        """ Test that we can incrementatlly build the commit state and
        roll it back
        """
        blocks = self.generate_chain(2)
        block, uncommitted_block = blocks
        commit_state = self.create_chain_commit_state(
            blocks=blocks,
            uncommitted_blocks=[uncommitted_block],
        )
        self.commit_state = commit_state

        # the first block is still present
        self.assert_block_present(block)
        # batch from the uncommited block should not be present
        self.assert_block_not_present(uncommitted_block)

        batch = uncommitted_block.batches[0]
        commit_state.add_batch(batch)

        # the batch should appear present
        self.assert_batch_present(batch)

        # check that we can remove the batch again.
        commit_state.remove_batch(batch)
        self.assert_batch_not_present(batch)

        # Do an incremental add of the batch
        for txn in batch.transactions:
            commit_state.add_txn(txn.header_signature)
            self.assert_txn_present(txn)
        self.assertFalse(commit_state.has_batch(batch.header_signature))

        commit_state.add_batch(batch, add_transactions=False)
        self.assert_batch_present(batch)

        # check that we can remove the batch again.
        commit_state.remove_batch(batch)
        self.assert_batch_not_present(batch)

    def generate_chain(self, block_count):
        return self.block_tree_manager.generate_chain(
            None, [{} for _ in range(block_count)])

    def create_chain_commit_state(self,
                                  blocks,
                                  uncommitted_blocks=None,
                                  chain_head=None):
        block_store = BlockStore(DictDatabase())
        block_store.update_chain(blocks)
        if chain_head is None:
            chain_head = block_store.chain_head.identifier
        if uncommitted_blocks is None:
            uncommitted_blocks = []
        return ChainCommitState(block_store, uncommitted_blocks)

    def assert_txn_present(self, txn):
        self.assertTrue(self.commit_state.has_transaction(
            txn.header_signature))

    def assert_batch_present(self, batch):
        self.assertTrue(self.commit_state.has_batch(batch.header_signature))
        for txn in batch.transactions:
            self.assert_txn_present(txn)

    def assert_block_present(self, block):
        for batch in block.batches:
            self.assert_batch_present(batch)

    def assert_txn_not_present(self, txn):
        self.assertFalse(
            self.commit_state.has_transaction(txn.header_signature))

    def assert_batch_not_present(self, batch):
        self.assertFalse(self.commit_state.has_batch(batch.header_signature))
        for txn in batch.transactions:
            self.assert_txn_not_present(txn)

    def assert_block_not_present(self, block):
        for batch in block.batches:
            self.assert_batch_not_present(batch)

    def assert_missing(self):
        """check missing keys behave as expected.
        """
        self.assertFalse(self.commit_state.has_batch("missing"))
        self.assertFalse(self.commit_state.has_transaction("missing"))
예제 #10
0
class TestBlockValidator(unittest.TestCase):
    def setUp(self):
        self.btm = BlockTreeManager()

    def create_block_validator(self, new_block, on_block_validated):
        return BlockValidator(consensus=TestModeVerifier(),
                              new_block=new_block,
                              chain_head=self.btm.chain_head,
                              block_cache=self.btm.block_cache,
                              done_cb=on_block_validated,
                              executor=MockTransactionExecutor(),
                              squash_handler=None)

    class BlockValidationHandler(object):
        def __init__(self):
            self.result = None

        def on_block_validated(self, commit_new_block, result):
            result["commit_new_block"] = commit_new_block
            self.result = result

        def has_result(self):
            return self.result is not None

    # fork based tests
    def test_fork_simple(self):
        """
        Test a simple case of a new block extending the current root.
        """
        bvh = self.BlockValidationHandler()
        new_block = self.btm.generate_block(previous_block=self.btm.chain_head,
                                            add_to_store=True)

        bv = self.create_block_validator(new_block, bvh.on_block_validated)
        bv.run()

        self.assertTrue(bvh.has_result())
        self.assertTrue(new_block.status == BlockStatus.Valid)
        self.assertTrue(bvh.result["commit_new_block"])

    def test_good_fork_lower(self):
        """
        Test case of a new block extending on a valid chain but not as long
        as the current chain.
        """
        bvh = self.BlockValidationHandler()

        root = self.btm.chain_head

        # create a new valid chain 5 long from the current root
        new_head = self.btm.generate_chain(root, 5, {'add_to_store': True})
        self.btm.set_chain_head(new_head[-1])
        # generate candidate chain 3 long from the same root
        new_block = self.btm.generate_chain(root, 3, {'add_to_cache': True})

        bv = self.create_block_validator(new_block[-1], bvh.on_block_validated)
        bv.run()

        self.assertTrue(bvh.has_result())
        self.assertTrue(new_block[-1].status == BlockStatus.Valid)
        self.assertFalse(bvh.result["commit_new_block"])

    def test_good_fork_higher(self):
        """
        Test case of a new block extending on a valid chain but longer
        than the current chain. ( similar to test_good_fork_lower but uses
        a different code path when finding the common root )
        """
        bvh = self.BlockValidationHandler()

        root = self.btm.chain_head

        # create a new valid chain 5 long from the current root
        new_head = self.btm.generate_chain(root, 5, {'add_to_store': True})
        self.btm.set_chain_head(new_head[-1])
        # generate candidate chain 8 long from the same root
        new_block = self.btm.generate_chain(root, 8, {'add_to_cache': True})
        bv = self.create_block_validator(new_block[-1], bvh.on_block_validated)
        bv.run()

        self.assertTrue(bvh.has_result())
        self.assertTrue(new_block[-1].status == BlockStatus.Valid)
        self.assertTrue(bvh.result["commit_new_block"])

    def test_fork_different_genesis(self):
        """"
        Test the case where new block is from a different genesis
        """
        bvh = self.BlockValidationHandler()

        # create a new valid chain 5 long from the current root
        new_head = self.btm.generate_chain(self.btm.chain_head, 5,
                                           {'add_to_store': True})
        self.btm.set_chain_head(new_head[-1])

        # generate candidate chain 5 long from it's own genesis
        new_block = self.btm.generate_chain(None, 5, {'add_to_cache': True})

        bv = self.create_block_validator(new_block[-1], bvh.on_block_validated)
        bv.run()

        self.assertTrue(bvh.has_result())
        self.assertTrue(new_block[-1].status == BlockStatus.Invalid)
        self.assertFalse(bvh.result["commit_new_block"])

    def test_fork_missing_predecessor(self):
        """"
        Test the case where new block is missing the a predecessor
        """
        bvh = self.BlockValidationHandler()

        root = self.btm.chain_head

        # generate candidate chain 3 long off the current head.
        new_block = self.btm.generate_chain(root, 3, {'add_to_cache': True})
        # remove one of the new blocks
        del self.btm.block_cache[new_block[1].identifier]

        bv = self.create_block_validator(new_block[-1], bvh.on_block_validated)
        bv.run()

        self.assertTrue(bvh.has_result())
        self.assertTrue(new_block[-1].status == BlockStatus.Invalid)
        self.assertFalse(bvh.result["commit_new_block"])

    def test_fork_invalid_predecessor(self):
        """"
        Test the case where new block has an invalid predecessor
        """
        bvh = self.BlockValidationHandler()

        root = self.btm.chain_head

        # generate candidate chain 3 long off the current head.
        new_block = self.btm.generate_chain(root, 3, {'add_to_cache': True})
        # Mark a predecessor as invalid
        new_block[1].status = BlockStatus.Invalid

        bv = self.create_block_validator(new_block[-1], bvh.on_block_validated)
        bv.run()

        self.assertTrue(bvh.has_result())
        self.assertTrue(new_block[-1].status == BlockStatus.Invalid)
        self.assertFalse(bvh.result["commit_new_block"])

    # block based tests
    def test_block_bad_signature(self):
        """
        Test the case where the new block has a bad signature.
        """
        pass

    def test_block_missing_batch(self):
        """
        Test the case where the new block is missing a batch.
        """
        pass

    def test_block_extra_batch(self):
        """
        Test the case where the new block has a batch.
        """
        pass

    def test_block_batches_order(self):
        """
        Test the case where the new block has batches that are
        out of order.
        """
        pass

    def test_block_bad_batch(self):
        """
        Test the case where the new block has a bad batch
        """
        pass

    def test_block_missing_batch_dependency(self):
        """
        Test the case where the new block has a batch that is missing a
        dependency.
        """
        pass

    def test_block_bad_state(self):
        """
        Test the case where the new block has a bad batch
        """
        pass

    def test_block_bad_consensus(self):
        """
        Test the case where the new block has a bad batch
        """
        pass
예제 #11
0
class TestChainController(unittest.TestCase):
    def setUp(self):
        self.blocks = BlockTreeManager()
        self.gossip = MockNetwork()
        self.executor = SynchronousExecutor()
        self.txn_executor = MockTransactionExecutor()

        def chain_updated(head):
            pass

        self.chain_ctrl = ChainController(
            consensus=TestModeVerifier(),
            block_store=self.blocks.block_store,
            send_message=self.gossip.send_message,
            executor=self.executor,
            transaction_executor=MockTransactionExecutor(),
            on_chain_updated=chain_updated,
            squash_handler=None)

    def test_simple_case(self):
        # TEST Run the simple case
        block_1 = self.blocks.generate_block(self.blocks.chain_head)
        self.chain_ctrl.on_block_received(block_1.get_block())
        self.executor.process_all()
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                block_1.header_signature)

    def test_alternate_genesis(self):
        # TEST Run generate and alternate genesis block
        head = self.chain_ctrl.chain_head
        other_genesis = self.blocks.generate_block(add_to_store=True)
        for b in self.blocks.generate_chain(other_genesis, 5):
            self.chain_ctrl.on_block_received(b.get_block())
            self.executor.process_all()
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                head.block.header_signature)

    def test_bad_block_signature(self):
        # TEST Bad block extending current chain
        # Bad due to signature
        head = self.blocks.chain_head
        block_bad = self.blocks.generate_block(self.blocks.chain_head.block,
                                               invalid_signature=True)
        self.chain_ctrl.on_block_received(block_bad.get_block())
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                head.block.header_signature)

    def test_bad_block_consensus(self):
        # Bad due to consensus
        pass

    def test_bad_block_transaction(self):
        # Bad due to transaction
        pass

    def test_missing_block(self):
        # TEST Missing block G->missing->B
        head = self.blocks.chain_head
        new_blocks = self.blocks.generate_chain(head, 2)
        self.chain_ctrl.on_block_received(new_blocks[1].get_block())
        self.executor.process_all()
        assert (len(self.gossip.messages) == 1)
        block_id = self.gossip.messages[0]
        assert (block_id == new_blocks[0].header_signature)
        self.gossip.clear()
        self.chain_ctrl.on_block_received(new_blocks[0].get_block())
        self.executor.process_all()
        assert (self.chain_ctrl.chain_head.block.header_signature ==
                new_blocks[1].header_signature)

    def test_missing_block_invalid_head(self):
        # TEST Missing block G->missing->B
        #  B is invalid but Missing is valid
        head = self.blocks.chain_head
        new_blocks_def = self.blocks.generate_chain_definition(2)
        new_blocks_def[1]["invalid_signature"] = True
        new_blocks = self.blocks.generate_chain(head, new_blocks_def)
        self.chain_ctrl.on_block_received(new_blocks[1].get_block())
        self.executor.process_all()
        assert (len(self.gossip.messages) == 1)
        block_id = self.gossip.messages[0]
        assert (block_id == new_blocks[0].header_signature)
        self.gossip.clear()
        self.chain_ctrl.on_block_received(new_blocks[0].get_block())
        self.executor.process_all()

        pp.pprint(new_blocks)
        pp.pprint(self.blocks.block_store)
예제 #12
0
class TestChainController(unittest.TestCase):
    def setUp(self):
        self.block_tree_manager = BlockTreeManager()
        self.gossip = MockNetwork()
        self.executor = SynchronousExecutor()
        self.txn_executor = MockTransactionExecutor()
        self.block_sender = MockBlockSender()
        self.chain_id_manager = MockChainIdManager()
        self._chain_head_lock = RLock()
        self.state_delta_processor = MockStateDeltaProcessor()

        def chain_updated(head, committed_batches=None,
                          uncommitted_batches=None):
            pass

        self.chain_ctrl = ChainController(
            block_cache=self.block_tree_manager.block_cache,
            state_view_factory=MockStateViewFactory(
                self.block_tree_manager.state_db),
            block_sender=self.block_sender,
            executor=self.executor,
            transaction_executor=MockTransactionExecutor(
                batch_execution_result=None),
            chain_head_lock=self._chain_head_lock,
            on_chain_updated=chain_updated,
            squash_handler=None,
            chain_id_manager=self.chain_id_manager,
            state_delta_processor=self.state_delta_processor,
            identity_signing_key=self.block_tree_manager.identity_signing_key,
            data_dir=None,
            config_dir=None)

        init_root = self.chain_ctrl.chain_head
        self.assert_is_chain_head(init_root)

        # create a chain of length 5 extending the root
        _, head = self.generate_chain(init_root, 5)
        self.receive_and_process_blocks(head)
        self.assert_is_chain_head(head)

        self.init_head = head

    def test_simple_case(self):
        new_block = self.generate_block(self.init_head)
        self.receive_and_process_blocks(new_block)
        self.assert_is_chain_head(new_block)
        # validate that the deltas for the new block are published
        self.assertEqual(new_block, self.state_delta_processor.block)

    def test_alternate_genesis(self):
        '''Tests a fork extending an alternate genesis block
        '''
        chain, head = self.generate_chain(None, 5)

        for block in chain:
            self.receive_and_process_blocks(block)

        # make sure initial head is still chain head
        self.assert_is_chain_head(self.init_head)

    def test_bad_blocks(self):
        '''Tests bad blocks extending current chain
        '''
        # Bad due to consensus
        bad_consen = self.generate_block(
            previous_block=self.init_head,
            invalid_consensus=True)

        # chain head should be the same
        self.receive_and_process_blocks(bad_consen)
        self.assert_is_chain_head(self.init_head)

        # Bad due to transaction
        bad_batch = self.generate_block(
            previous_block=self.init_head,
            invalid_batch=True)

        # chain head should be the same
        self.receive_and_process_blocks(bad_batch)
        self.assert_is_chain_head(self.init_head)

        # # Ensure good block works
        good_block = self.generate_block(
            previous_block=self.init_head)

        # chain head should be good_block
        self.receive_and_process_blocks(good_block)
        self.assert_is_chain_head(good_block)

    def test_fork_weights(self):
        '''Tests extending blocks of different weights
        '''
        weight_4 = self.generate_block(
            previous_block=self.init_head,
            weight=4)

        weight_7 = self.generate_block(
            previous_block=self.init_head,
            weight=7)

        weight_8 = self.generate_block(
            previous_block=self.init_head,
            weight=8)

        self.receive_and_process_blocks(
            weight_7,
            weight_4,
            weight_8)

        self.assert_is_chain_head(weight_8)

    def test_fork_lengths(self):
        '''Tests competing forks of different lengths
        '''
        _, head_2 = self.generate_chain(self.init_head, 2)
        _, head_7 = self.generate_chain(self.init_head, 7)
        _, head_5 = self.generate_chain(self.init_head, 5)

        self.receive_and_process_blocks(
            head_2,
            head_7,
            head_5)

        self.assert_is_chain_head(head_7)

    def test_advancing_chain(self):
        '''Tests the chain being advanced between a fork's
        creation and validation
        '''
        _, fork_5 = self.generate_chain(self.init_head, 5)
        _, fork_3 = self.generate_chain(self.init_head, 3)

        self.receive_and_process_blocks(fork_3)
        self.assert_is_chain_head(fork_3)

        # fork_5 is longer than fork_3, so it should be accepted
        self.receive_and_process_blocks(fork_5)
        self.assert_is_chain_head(fork_5)

    def test_fork_missing_block(self):
        '''Tests a fork with a missing block
        '''
        # make new chain
        new_chain, new_head = self.generate_chain(self.init_head, 5)

        self.chain_ctrl.on_block_received(new_head)

        # delete a block from the new chain
        del self.chain_ctrl._block_cache[new_chain[3].identifier]

        self.executor.process_all()

        # chain shouldn't advance
        self.assert_is_chain_head(self.init_head)

        # try again, chain still shouldn't advance
        self.receive_and_process_blocks(new_head)

        self.assert_is_chain_head(self.init_head)

    def test_fork_bad_block(self):
        '''Tests a fork with a bad block in the middle
        '''
        # make two chains extending chain
        good_chain, good_head = self.generate_chain(self.init_head, 5)
        bad_chain, bad_head = self.generate_chain(self.init_head, 5)

        self.chain_ctrl.on_block_received(bad_head)
        self.chain_ctrl.on_block_received(good_head)

        # invalidate block in the middle of bad_chain
        bad_chain[3].status = BlockStatus.Invalid

        self.executor.process_all()

        # good_chain should be accepted
        self.assert_is_chain_head(good_head)

    def test_advancing_fork(self):
        '''Tests a fork advancing before getting validated
        '''
        _, fork_head = self.generate_chain(self.init_head, 5)

        self.chain_ctrl.on_block_received(fork_head)

        # advance fork before it gets accepted
        _, ext_head = self.generate_chain(fork_head, 3)

        self.executor.process_all()

        self.assert_is_chain_head(fork_head)

        self.receive_and_process_blocks(ext_head)

        self.assert_is_chain_head(ext_head)

    def test_block_extends_in_validation(self):
        '''Tests a block getting extended while being validated
        '''
        # create candidate block
        candidate = self.block_tree_manager.generate_block(
            previous_block=self.init_head)

        self.assert_is_chain_head(self.init_head)

        # queue up the candidate block, but don't process
        self.chain_ctrl.on_block_received(candidate)

        # create a new block extending the candidate block
        extending_block = self.block_tree_manager.generate_block(
            previous_block=candidate)

        self.assert_is_chain_head(self.init_head)

        # queue and process the extending block,
        # which should be the new head
        self.receive_and_process_blocks(extending_block)
        self.assert_is_chain_head(extending_block)

    def test_multiple_extended_forks(self):
        '''A more involved example of competing forks

        Three forks of varying lengths a_0, b_0, and c_0
        are created extending the existing chain, with c_0
        being the longest initially. The chains are extended
        in the following sequence:

        1. Extend all forks by 2. The c fork should remain the head.
        2. Extend forks by lenths such that the b fork is the
           longest. It should be the new head.
        3. Extend all forks by 8. The b fork should remain the head.
        4. Create a new fork of the initial chain longer than
           any of the other forks. It should be the new head.
        '''

        # create forks of various lengths
        _, a_0 = self.generate_chain(self.init_head, 3)
        _, b_0 = self.generate_chain(self.init_head, 5)
        _, c_0 = self.generate_chain(self.init_head, 7)

        self.receive_and_process_blocks(a_0, b_0, c_0)
        self.assert_is_chain_head(c_0)

        # extend every fork by 2
        _, a_1 = self.generate_chain(a_0, 2)
        _, b_1 = self.generate_chain(b_0, 2)
        _, c_1 = self.generate_chain(c_0, 2)

        self.receive_and_process_blocks(a_1, b_1, c_1)
        self.assert_is_chain_head(c_1)

        # extend the forks by different lengths
        _, a_2 = self.generate_chain(a_1, 1)
        _, b_2 = self.generate_chain(b_1, 6)
        _, c_2 = self.generate_chain(c_1, 3)

        self.receive_and_process_blocks(a_2, b_2, c_2)
        self.assert_is_chain_head(b_2)

        # extend every fork by 2
        _, a_3 = self.generate_chain(a_2, 8)
        _, b_3 = self.generate_chain(b_2, 8)
        _, c_3 = self.generate_chain(c_2, 8)

        self.receive_and_process_blocks(a_3, b_3, c_3)
        self.assert_is_chain_head(b_3)

        # create a new longest chain
        _, wow = self.generate_chain(self.init_head, 30)
        self.receive_and_process_blocks(wow)
        self.assert_is_chain_head(wow)

    # next multi threaded
    # next add block publisher
    # next batch lists
    # integrate with LMDB
    # early vs late binding ( class member of consensus BlockPublisher)

    # helpers

    def assert_is_chain_head(self, block):
        chain_head_sig = self.chain_ctrl.chain_head.header_signature
        block_sig = block.header_signature

        self.assertEqual(
            chain_head_sig[:8],
            block_sig[:8],
            'Not chain head')

    def generate_chain(self, root_block, num_blocks,
                                 params={'add_to_cache': True}):
        '''Returns (chain, chain_head).
        Usually only the head is needed,
        but occasionally the chain itself is used.
        '''
        chain = self.block_tree_manager.generate_chain(
            root_block, num_blocks, params)

        head = chain[-1]

        return chain, head

    def generate_block(self, *args, **kwargs):
        return self.block_tree_manager.generate_block(
            *args, **kwargs)

    def receive_and_process_blocks(self, *blocks):
        for block in blocks:
            self.chain_ctrl.on_block_received(block)
        self.executor.process_all()
예제 #13
0
class TestBlockValidator(unittest.TestCase):
    def setUp(self):
        self.state_view_factory = MockStateViewFactory()

        self.block_tree_manager = BlockTreeManager()
        self.root = self.block_tree_manager.chain_head

        self.block_validation_handler = self.BlockValidationHandler()

    # fork based tests
    def test_fork_simple(self):
        """
        Test a simple case of a new block extending the current root.
        """

        new_block = self.block_tree_manager.generate_block(
            previous_block=self.root)

        self.validate_block(new_block)

        self.assert_valid_block(new_block)
        self.assert_new_block_committed()

    def test_good_fork_lower(self):
        """
        Test case of a new block extending on a valid chain but not as long
        as the current chain.
        """
        # create a new valid chain 5 long from the current root
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        self.block_tree_manager.set_chain_head(head)

        # generate candidate chain 3 long from the same root
        new_chain, new_head = self.generate_chain_with_head(
            self.root, 3, {'add_to_cache': True})

        self.validate_block(new_head)

        self.assert_valid_block(new_head)
        self.assert_new_block_not_committed()

    def test_good_fork_higher(self):
        """
        Test case of a new block extending on a valid chain but longer
        than the current chain. ( similar to test_good_fork_lower but uses
        a different code path when finding the common root )
        """
        # create a new valid chain 5 long from the current root
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        self.block_tree_manager.set_chain_head(head)

        # generate candidate chain 8 long from the same root
        new_chain, new_head = self.generate_chain_with_head(
            head, 8, {'add_to_cache': True})

        self.validate_block(new_head)

        self.assert_valid_block(new_head)
        self.assert_new_block_committed()

    def test_fork_different_genesis(self):
        """"
        Test the case where new block is from a different genesis
        """
        # create a new valid chain 5 long from the current root
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        self.block_tree_manager.set_chain_head(head)

        # generate candidate chain 5 long from its own genesis
        new_chain, new_head = self.generate_chain_with_head(
            None, 5, {'add_to_cache': True})

        self.validate_block(new_head)

        self.assert_invalid_block(new_head)
        self.assert_new_block_not_committed()

    def test_fork_missing_predecessor(self):
        """"
        Test the case where new block is missing the a predecessor
        """
        # generate candidate chain 5 long off the current head.
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_cache': True})

        # remove one of the new blocks
        del self.block_tree_manager.block_cache[chain[1].identifier]

        self.validate_block(head)

        self.assert_invalid_block(head)
        self.assert_new_block_not_committed()

    def test_fork_invalid_predecessor(self):
        """"
        Test the case where new block has an invalid predecessor
        """
        # generate candidate chain 5 long off the current head.
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_cache': True})

        # Mark a predecessor as invalid
        chain[1].status = BlockStatus.Invalid

        self.validate_block(head)

        self.assert_invalid_block(head)
        self.assert_new_block_not_committed()

    def test_block_bad_consensus(self):
        """
        Test the case where the new block has a bad batch
        """
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        new_block = self.block_tree_manager.generate_block(
            previous_block=head,
            add_to_cache=True,
            invalid_consensus=True)

        self.validate_block(new_block)

        self.assert_invalid_block(new_block)
        self.assert_new_block_not_committed()

    def test_block_bad_batch(self):
        """
        Test the case where the new block has a bad batch
        """
        chain, head = self.generate_chain_with_head(
            self.root, 5, {'add_to_store': True})

        new_block = self.block_tree_manager.generate_block(
            previous_block=head,
            add_to_cache=True,
            invalid_batch=True)

        self.validate_block(new_block)

        self.assert_invalid_block(new_block)
        self.assert_new_block_not_committed()


    def test_block_missing_batch_dependency(self):
        """
        Test the case where the new block has a batch that is missing a
        dependency.
        """
        pass

    # assertions

    def assert_valid_block(self, block):
        self.assertEqual(
            block.status, BlockStatus.Valid,
            "Block should be valid")

    def assert_invalid_block(self, block):
        self.assertEqual(
            block.status, BlockStatus.Invalid,
            "Block should be invalid")

    def assert_new_block_committed(self):
        self.assert_handler_has_result()
        self.assertTrue(
            self.block_validation_handler.result["commit_new_block"],
            "New block not committed, should be")

    def assert_new_block_not_committed(self):
        self.assert_handler_has_result()
        self.assertFalse(
            self.block_validation_handler.result["commit_new_block"],
            "New block committed, shouldn't be")

    def assert_handler_has_result(self):
        msg = "Validation handler doesn't have result"
        self.assertTrue(self.block_validation_handler.has_result(), msg)

    # block validation

    def validate_block(self, block):
        validator = self.create_block_validator(
            block,
            self.block_validation_handler.on_block_validated)

        validator.run()

    def create_block_validator(self, new_block, on_block_validated):
        return BlockValidator(
            consensus_module=mock_consensus,
            new_block=new_block,
            chain_head=self.block_tree_manager.chain_head,
            state_view_factory=self.state_view_factory,
            block_cache=self.block_tree_manager.block_cache,
            done_cb=on_block_validated,
            executor=MockTransactionExecutor(batch_execution_result=None),
            squash_handler=None,
            identity_signing_key=self.block_tree_manager.identity_signing_key,
            data_dir=None,
            config_dir=None)

    class BlockValidationHandler(object):
        def __init__(self):
            self.result = None

        def on_block_validated(self, commit_new_block, result):
            result["commit_new_block"] = commit_new_block
            self.result = result

        def has_result(self):
            return self.result is not None

    # block tree manager interface

    def generate_chain_with_head(self, root_block, num_blocks, params=None):
        chain = self.block_tree_manager.generate_chain(
            root_block, num_blocks, params)

        head = chain[-1]

        return chain, head