Пример #1
0
    def _decanonicalize_descendant_orphans(
            cls,
            db: DatabaseAPI,
            header: BlockHeaderAPI,
            checkpoints: Tuple[Hash32, ...]) -> None:

        # Determine if any children need to be de-canonicalized because they are not children of
        #   the new chain head
        new_gaps = starting_gaps = cls._get_header_chain_gaps(db)

        child_number = BlockNumber(header.block_number + 1)
        try:
            child = cls._get_canonical_block_header_by_number(db, child_number)
        except HeaderNotFound:
            # There is no canonical block here
            next_invalid_child = None
        else:
            if child.parent_hash != header.hash:
                if child.hash in checkpoints:
                    raise CheckpointsMustBeCanonical(
                        f"Trying to decanonicalize {child} while making {header} the chain tip"
                    )
                else:
                    next_invalid_child = child
            else:
                next_invalid_child = None

        while next_invalid_child:
            # decanonicalize, and add gap for tracking
            db.delete(SchemaV1.make_block_number_to_hash_lookup_key(child_number))
            new_gaps = reopen_gap(child_number, new_gaps)

            # find next child
            child_number = BlockNumber(child_number + 1)
            try:
                # All contiguous children must now be made invalid
                next_invalid_child = cls._get_canonical_block_header_by_number(db, child_number)
            except HeaderNotFound:
                # Found the end of this streak of canonical blocks
                break
            else:
                if next_invalid_child.hash in checkpoints:
                    raise CheckpointsMustBeCanonical(
                        f"Trying to decanonicalize {next_invalid_child} while making {header} the"
                        " chain tip"
                    )

        if new_gaps != starting_gaps:
            db.set(
                SchemaV1.make_header_chain_gaps_lookup_key(),
                rlp.encode(new_gaps, sedes=chain_gaps)
            )
Пример #2
0
    def _handle_gap_change(cls, db: DatabaseAPI, gap_info: GapInfo,
                           header: BlockHeaderAPI,
                           genesis_parent_hash: Hash32) -> ChainGaps:

        gap_change, gaps = gap_info
        if gap_change not in GAP_WRITES:
            return gaps

        # Check if this change will link up the chain to the right
        if gap_change in (GapChange.GapFill, GapChange.GapRightShrink):
            next_child_number = BlockNumber(header.block_number + 1)
            expected_child = cls._get_canonical_block_header_by_number(
                db, next_child_number)
            if header.hash != expected_child.parent_hash:
                # Must not join a canonical chain that is not linked from parent to child
                # If the child is a checkpoint, reject this fill as an uncle.
                checkpoints = cls._get_checkpoints(db)
                if expected_child.hash in checkpoints:
                    raise CheckpointsMustBeCanonical(
                        f"Cannot make {header} canonical, because it is not the parent of"
                        f" declared checkpoint: {expected_child}")
                else:
                    # If the child is *not* a checkpoint, then re-open a gap in the chain
                    gaps = cls._decanonicalize_single(
                        db, expected_child.block_number, gaps)

        # We implicitly assert that persisted headers are canonical here.
        # This assertion is made when persisting headers that are known to be part of a gap
        # in the canonical chain.
        # What if this assertion is later found to be false? At gap fill time, we can detect if the
        # chains don't link (and raise a ValidationError). Also, when a true canonical header is
        # added eventually, we need to canonicalize all the true headers.
        cls._canonicalize_header(db, header, genesis_parent_hash)
        return gaps
Пример #3
0
    def _canonicalize_header(
        cls,
        db: DatabaseAPI,
        header: BlockHeaderAPI,
        genesis_parent_hash: Hash32,
    ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]:
        """
        Force this header to be canonical, and adjust its ancestors/descendants as necessary

        :raises CheckpointsMustBeCanonical: if trying to set a head that would
            de-canonicalize a checkpoint
        """
        new_canonical_headers = cast(
            Tuple[BlockHeaderAPI, ...],
            tuple(
                reversed(
                    cls._find_new_ancestors(db, header, genesis_parent_hash))))
        old_canonical_headers = cls._find_headers_to_decanonicalize(
            db,
            [h.block_number for h in new_canonical_headers],
        )

        # Reject if this would make a checkpoint non-canonical
        checkpoints = cls._get_checkpoints(db)
        attempted_checkpoint_overrides = set(old
                                             for old in old_canonical_headers
                                             if old.hash in checkpoints)
        if len(attempted_checkpoint_overrides):
            raise CheckpointsMustBeCanonical(
                f"Tried to switch chain away from checkpoint(s) {attempted_checkpoint_overrides!r}"
                f" by inserting new canonical headers {new_canonical_headers}")

        for ancestor in new_canonical_headers:
            cls._add_block_number_to_hash_lookup(db, ancestor)

        if len(new_canonical_headers):
            cls._decanonicalize_descendant_orphans(db,
                                                   new_canonical_headers[-1],
                                                   checkpoints)

        return new_canonical_headers, old_canonical_headers