Example #1
0
    def import_block(
        self,
        block: BaseBeaconBlock,
        perform_validation: bool = True
    ) -> Tuple[BaseBeaconBlock, Tuple[BaseBeaconBlock, ...], Tuple[
            BaseBeaconBlock, ...]]:
        """
        Import a complete block and returns a 3-tuple

        - the imported block
        - a tuple of blocks which are now part of the canonical chain.
        - a tuple of blocks which were canonical and now are no longer canonical.
        """
        self.logger.debug(
            "attempting import of block with slot %s and signing_root %s",
            block.slot,
            humanize_hash(block.signing_root),
        )

        try:
            parent_block = self.get_block_by_root(block.parent_root)
        except BlockNotFound:
            raise ValidationError(
                "Attempt to import block #{}.  Cannot import block {} before importing "
                "its parent block at {}".format(
                    block.slot,
                    humanize_hash(block.signing_root),
                    humanize_hash(block.parent_root),
                ))

        state_machine = self.get_state_machine(at_slot=parent_block.slot)
        state_class = state_machine.get_state_class()
        state = self.chaindb.get_state_by_root(parent_block.state_root,
                                               state_class)

        state, imported_block = state_machine.import_block(
            block, state, check_proposer_signature=perform_validation)

        # Validate the imported block.
        if perform_validation:
            validate_imported_block_unchanged(imported_block, block)

        # TODO: Now it just persists all state. Should design how to clean up the old state.
        self.chaindb.persist_state(state)

        fork_choice_scoring = state_machine.get_fork_choice_scoring()
        (new_canonical_blocks,
         old_canonical_blocks) = self.chaindb.persist_block(
             imported_block, imported_block.__class__, fork_choice_scoring)

        self.logger.debug(
            "successfully imported block at slot %s with signing root %s",
            imported_block.slot,
            humanize_hash(imported_block.signing_root),
        )

        return imported_block, new_canonical_blocks, old_canonical_blocks
Example #2
0
    def import_block(
        self,
        block: BaseBeaconBlock,
        perform_validation: bool = True
    ) -> Tuple[BaseBeaconBlock, Tuple[BaseBeaconBlock, ...], Tuple[
            BaseBeaconBlock, ...]]:
        """
        Import a complete block and returns a 3-tuple

        - the imported block
        - a tuple of blocks which are now part of the canonical chain.
        - a tuple of blocks which were canonical and now are no longer canonical.
        """

        try:
            parent_block = self.get_block_by_root(block.parent_root)
        except BlockNotFound:
            raise ValidationError(
                "Attempt to import block #{}.  Cannot import block {} before importing "
                "its parent block at {}".format(block.slot, block.signing_root,
                                                block.parent_root))

        head_state_slot = self.chaindb.get_head_state_slot()
        if head_state_slot >= block.slot:
            # Importing a block older than the head state. Hence head state can not be used to
            # perform state transition.
            prev_state_slot = parent_block.slot
        else:
            prev_state_slot = head_state_slot

        state_machine = self.get_state_machine(prev_state_slot)
        state = self.get_state_by_slot(prev_state_slot)
        state, imported_block = state_machine.import_block(
            block, state, check_proposer_signature=perform_validation)

        # Validate the imported block.
        if perform_validation:
            validate_imported_block_unchanged(imported_block, block)

        # TODO: Now it just persists all state. Should design how to clean up the old state.
        self.chaindb.persist_state(state)

        fork_choice_scoring = state_machine.get_fork_choice_scoring()
        (new_canonical_blocks,
         old_canonical_blocks) = self.chaindb.persist_block(
             imported_block, imported_block.__class__, fork_choice_scoring)

        self.logger.debug(
            "IMPORTED_BLOCK: slot %s | signed root %s",
            imported_block.slot,
            encode_hex(imported_block.signing_root),
        )

        return imported_block, new_canonical_blocks, old_canonical_blocks
Example #3
0
    def import_block(
        self,
        block: BaseBeaconBlock,
        perform_validation: bool = True
    ) -> Tuple[BaseBeaconBlock, Tuple[BaseBeaconBlock, ...], Tuple[
            BaseBeaconBlock, ...]]:
        """
        Import a complete block and returns a 3-tuple

        - the imported block
        - a tuple of blocks which are now part of the canonical chain.
        - a tuple of blocks which were canonical and now are no longer canonical.
        """

        try:
            parent_block = self.get_block_by_root(block.previous_block_root)
        except BlockNotFound:
            raise ValidationError(
                "Attempt to import block #{}.  Cannot import block {} before importing "
                "its parent block at {}".format(
                    block.slot,
                    block.signing_root,
                    block.previous_block_root,
                ))
        base_block_for_import = self.create_block_from_parent(
            parent_block,
            FromBlockParams(),
        )
        state, imported_block = self.get_state_machine(
            base_block_for_import).import_block(block)

        # Validate the imported block.
        if perform_validation:
            validate_imported_block_unchanged(imported_block, block)

        # TODO: Now it just persists all state. Should design how to clean up the old state.
        self.chaindb.persist_state(state)

        (
            new_canonical_blocks,
            old_canonical_blocks,
        ) = self.chaindb.persist_block(imported_block,
                                       imported_block.__class__)

        self.logger.debug(
            'IMPORTED_BLOCK: slot %s | signed root %s',
            imported_block.slot,
            encode_hex(imported_block.signing_root),
        )

        return imported_block, new_canonical_blocks, old_canonical_blocks
Example #4
0
def FuzzerRunOne(input_data: bytes) -> typing.Optional[bytes]:
    test_case = ssz.decode(input_data, BlockTestCase)

    try:
        post = st_instance.apply_state_transition(
            state=test_case.pre,
            block=test_case.block,
            check_proposer_signature=True,
        )
        if VALIDATE_STATE_ROOT:
            # NOTE trinity performs state root validation at a higher level
            # so we perform it here if needed.
            # See https://github.com/ethereum/trinity/issues/1340
            # https://github.com/ethereum/trinity/blob/0d53ef2cf57458d11c5a5b0e5546b026f2fce3f9/eth2/beacon/chains/base.py#L413-L415

            # TODO when updating spec versions, ensure this matches the relevant
            # Trinity code
            post_block = test_case.block.copy(state_root=post.hash_tree_root)
            # Raises a ValidationError if state_roots are different
            validate_imported_block_unchanged(test_case.block, post_block)
    except ValidationError as e:
        return None
    # NOTE - signature verification should do nothing with bls disabled
    return ssz.encode(post)
Example #5
0
    def _import_block(
            self,
            block: BaseSignedBeaconBlock,
            perform_validation: bool = True) -> BaseSignedBeaconBlock:
        try:
            # NOTE: would need to introduce a "root to slot" look up here for polymorphism
            parent_block = self._chain_db.get_block_by_root(
                block.parent_root, BeaconBlock)
        except BlockNotFound:
            raise ParentNotFoundError(
                f"attempt to import block {block} but missing parent block")

        # NOTE: check if block is in the canonical chain
        # First, see if we have a block already at that slot...
        existing_block = self._get_block_by_slot(block.slot, block.__class__)
        if existing_block:
            if existing_block != block:
                # NOTE: we want to keep the block but avoid heavy state transition for now...
                # Rationale: this block may simply be a slashable block. It could also be on
                # a fork. Choose to defer the state materialization until we re-org via fork choice.
                self._chain_db.persist_block(block)

                raise SlashableBlockError(
                    block,
                    f"attempt to import {block} but canonical chain"
                    " already has a block at this slot",
                )
            else:
                # NOTE: block already imported!
                return block
        else:
            head = self.get_canonical_head()
            extension_of_head = block.parent_root == head.hash_tree_root
            if not extension_of_head:
                # NOTE: this arm implies we received a block for a slot _ahead_ of our head
                # on the canonical chain...
                # NOTE: block validity _should_ reject a block before it gets to this layer
                # but we will double-check in the event that invariant is violated or does not hold
                # NOTE: we elect to the block in the event of a
                # re-org later, but do no further processing.
                self._chain_db.persist_block(block)

                raise SlashableBlockError(
                    block,
                    f"attempt to import {block} but canonical chain is not as far ahead",
                )

        state_machine = self.get_state_machine(block.slot)
        pre_state = self._chain_db.get_state_by_root(parent_block.state_root,
                                                     state_machine.state_class,
                                                     state_machine.config)

        state, imported_block = state_machine.apply_state_transition(
            pre_state, block, check_proposer_signature=perform_validation)

        if perform_validation:
            validate_imported_block_unchanged(imported_block, block)

        # NOTE: if we have a valid block/state, then record in the database.
        self._chain_db.persist_block(block)
        self._chain_db.persist_state(state, state_machine.config)

        self._reconcile_justification_and_finality(state)

        return imported_block
Example #6
0
    def import_block(
        self,
        block: BaseBeaconBlock,
        perform_validation: bool = True
    ) -> Tuple[BaseBeaconBlock, Tuple[BaseBeaconBlock, ...], Tuple[
            BaseBeaconBlock, ...]]:
        """
        Import a complete block and returns a 3-tuple

        - the imported block
        - a tuple of blocks which are now part of the canonical chain.
        - a tuple of blocks which were canonical and now are no longer canonical.
        """

        try:
            parent_block = self.get_block_by_root(block.parent_root)
        except BlockNotFound:
            raise ValidationError(
                "Attempt to import block #{}.  Cannot import block {} before importing "
                "its parent block at {}".format(block.slot, block.signing_root,
                                                block.parent_root))

        try:
            canonical_root_at_slot = self.get_canonical_block_root(
                parent_block.slot)
        except BlockNotFound:
            # No corresponding block at this slot which means parent block is not
            # on canonical chain.
            is_new_canonical_block = False
        else:
            is_on_canonical_chain = parent_block.signing_root == canonical_root_at_slot
            is_newer_than_head_state_slot = (
                self.chaindb.get_head_state_slot() < block.slot)
            is_new_canonical_block = (is_on_canonical_chain
                                      and is_newer_than_head_state_slot)

        if is_new_canonical_block:
            state_machine = self.get_state_machine()
            state = self.get_head_state()
        else:
            state_machine = self.get_state_machine(at_slot=parent_block.slot)
            state = self.get_state_by_slot(parent_block.slot)

        state, imported_block = state_machine.import_block(
            block, state, check_proposer_signature=perform_validation)

        # Validate the imported block.
        if perform_validation:
            validate_imported_block_unchanged(imported_block, block)

        # TODO: Now it just persists all state. Should design how to clean up the old state.
        self.chaindb.persist_state(state)

        fork_choice_scoring = state_machine.get_fork_choice_scoring()
        (new_canonical_blocks,
         old_canonical_blocks) = self.chaindb.persist_block(
             imported_block, imported_block.__class__, fork_choice_scoring)

        self.logger.debug(
            "IMPORTED_BLOCK: slot %s | signed root %s",
            imported_block.slot,
            encode_hex(imported_block.signing_root),
        )

        return imported_block, new_canonical_blocks, old_canonical_blocks
Example #7
0
    def import_block(
        self, block: BaseSignedBeaconBlock, perform_validation: bool = True
    ) -> Tuple[
        BaseSignedBeaconBlock,
        Tuple[BaseSignedBeaconBlock, ...],
        Tuple[BaseSignedBeaconBlock, ...],
    ]:
        """
        Import a complete block and returns a 3-tuple

        - the imported block
        - a tuple of blocks which are now part of the canonical chain.
        - a tuple of blocks which were canonical and now are no longer canonical.
        """
        self.logger.debug("attempting import of block %s", block)

        try:
            parent_block = self.get_block_by_root(block.parent_root)
        except BlockNotFound:
            raise ParentNotFoundError(
                f"attempt to import block {block} but missing parent block"
            )

        try:
            existing_block = self.get_canonical_block_by_slot(block.slot)
            if existing_block != block:
                # NOTE: we want to keep the block but avoid heavy state transition for now...
                # Rationale: this block may simply be a slashable block. It could also be on
                # a fork. Choose to defer the state materialization until we re-org via fork choice.
                self.chaindb.write_signed_block(block)

                raise SlashableBlockError(
                    block,
                    f"attempt to import {block} but canonical chain"
                    " already has a block at this slot",
                )
            else:
                # NOTE: block already imported!
                return block, (), ()
        except BlockNotFound:
            # NOTE: block has not been imported for ``block.slot``
            pass

        state_machine = self.get_state_machine(at_slot=parent_block.slot)
        state_class = state_machine.state_class
        state = self.chaindb.get_state_by_root(
            parent_block.message.state_root, state_class
        )

        state, imported_block = state_machine.apply_state_transition(
            state, block, check_proposer_signature=perform_validation
        )

        # Validate the imported block.
        if perform_validation:
            validate_imported_block_unchanged(imported_block, block)

        fork_choice_scoring = state_machine.get_fork_choice_scoring()
        (
            new_canonical_blocks,
            old_canonical_blocks,
        ) = self._persist_validated_state_and_block(
            state, imported_block, fork_choice_scoring
        )

        self.logger.debug("successfully imported block %s", imported_block)

        return imported_block, new_canonical_blocks, old_canonical_blocks