def block_to_sub_block_record(
    constants: ConsensusConstants,
    sub_blocks: BlockchainInterface,
    required_iters: uint64,
    full_block: Optional[Union[FullBlock, HeaderBlock]],
    header_block: Optional[HeaderBlock],
):

    if full_block is None:
        assert header_block is not None
        block: Union[HeaderBlock, FullBlock] = header_block
    else:
        block = full_block
    if block.height == 0:
        prev_sb: Optional[SubBlockRecord] = None
        sub_slot_iters: uint64 = uint64(constants.SUB_SLOT_ITERS_STARTING)
    else:
        prev_sb = sub_blocks.sub_block_record(block.prev_header_hash)
        assert prev_sb is not None
        sub_slot_iters = get_next_sub_slot_iters(
            constants,
            sub_blocks,
            prev_sb.prev_hash,
            prev_sb.height,
            prev_sb.sub_slot_iters,
            prev_sb.deficit,
            len(block.finished_sub_slots) > 0,
            prev_sb.sp_total_iters(constants),
        )
    overflow = is_overflow_sub_block(constants, block.reward_chain_sub_block.signage_point_index)
    deficit = calculate_deficit(
        constants,
        block.height,
        prev_sb,
        overflow,
        len(block.finished_sub_slots),
    )
    prev_block_hash = block.foliage_block.prev_block_hash if block.foliage_block is not None else None
    timestamp = block.foliage_block.timestamp if block.foliage_block is not None else None
    fees = block.transactions_info.fees if block.transactions_info is not None else None
    reward_claims_incorporated = (
        block.transactions_info.reward_claims_incorporated if block.transactions_info is not None else None
    )

    if len(block.finished_sub_slots) > 0:
        finished_challenge_slot_hashes: Optional[List[bytes32]] = [
            sub_slot.challenge_chain.get_hash() for sub_slot in block.finished_sub_slots
        ]
        finished_reward_slot_hashes: Optional[List[bytes32]] = [
            sub_slot.reward_chain.get_hash() for sub_slot in block.finished_sub_slots
        ]
        finished_infused_challenge_slot_hashes: Optional[List[bytes32]] = [
            sub_slot.infused_challenge_chain.get_hash()
            for sub_slot in block.finished_sub_slots
            if sub_slot.infused_challenge_chain is not None
        ]
    elif block.height == 0:
        finished_challenge_slot_hashes = [constants.GENESIS_CHALLENGE]
        finished_reward_slot_hashes = [constants.GENESIS_CHALLENGE]
        finished_infused_challenge_slot_hashes = None
    else:
        finished_challenge_slot_hashes = None
        finished_reward_slot_hashes = None
        finished_infused_challenge_slot_hashes = None

    found_ses_hash: Optional[bytes32] = None
    ses: Optional[SubEpochSummary] = None
    if len(block.finished_sub_slots) > 0:
        for sub_slot in block.finished_sub_slots:
            if sub_slot.challenge_chain.subepoch_summary_hash is not None:
                found_ses_hash = sub_slot.challenge_chain.subepoch_summary_hash
    if found_ses_hash:
        assert prev_sb is not None
        assert len(block.finished_sub_slots) > 0
        ses = make_sub_epoch_summary(
            constants,
            sub_blocks,
            block.height,
            sub_blocks.sub_block_record(prev_sb.prev_hash),
            block.finished_sub_slots[0].challenge_chain.new_difficulty,
            block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters,
        )
        assert ses.get_hash() == found_ses_hash

    cbi = ChallengeBlockInfo(
        block.reward_chain_sub_block.proof_of_space,
        block.reward_chain_sub_block.challenge_chain_sp_vdf,
        block.reward_chain_sub_block.challenge_chain_sp_signature,
        block.reward_chain_sub_block.challenge_chain_ip_vdf,
    )

    if block.reward_chain_sub_block.infused_challenge_chain_ip_vdf is not None:
        icc_output: Optional[ClassgroupElement] = block.reward_chain_sub_block.infused_challenge_chain_ip_vdf.output
    else:
        icc_output = None

    prev_transaction_block_height = uint32(0)
    curr: Optional[SubBlockRecord] = sub_blocks.try_sub_block(block.prev_header_hash)
    while curr is not None and not curr.is_block:
        curr = sub_blocks.try_sub_block(curr.prev_hash)

    if curr is not None and curr.is_block:
        prev_transaction_block_height = curr.height

    return SubBlockRecord(
        block.header_hash,
        block.prev_header_hash,
        block.height,
        block.weight,
        block.total_iters,
        block.reward_chain_sub_block.signage_point_index,
        block.reward_chain_sub_block.challenge_chain_ip_vdf.output,
        icc_output,
        block.reward_chain_sub_block.get_hash(),
        cbi.get_hash(),
        sub_slot_iters,
        block.foliage_sub_block.foliage_sub_block_data.pool_target.puzzle_hash,
        block.foliage_sub_block.foliage_sub_block_data.farmer_reward_puzzle_hash,
        required_iters,
        deficit,
        overflow,
        prev_transaction_block_height,
        timestamp,
        prev_block_hash,
        fees,
        reward_claims_incorporated,
        finished_challenge_slot_hashes,
        finished_infused_challenge_slot_hashes,
        finished_reward_slot_hashes,
        ses,
    )
def validate_unfinished_header_block(
    constants: ConsensusConstants,
    sub_blocks: BlockchainInterface,
    header_block: UnfinishedHeaderBlock,
    check_filter: bool,
    expected_difficulty: uint64,
    expected_sub_slot_iters: uint64,
    skip_overflow_last_ss_validation: bool = False,
    skip_vdf_is_valid: bool = False,
) -> Tuple[Optional[uint64], Optional[ValidationError]]:
    """
    Validates an unfinished header block. This is a block without the infusion VDFs (unfinished)
    and without transactions and transaction info (header). Returns (required_iters, error).

    This method is meant to validate only the unfinished part of the sub-block. However, the finished_sub_slots
    refers to all sub-slots that were finishes from the previous sub-block's infusion point, up to this sub-blocks
    infusion point. Therefore, in the case where this is an overflow sub-block, and the last sub-slot is not yet
    released, header_block.finished_sub_slots will be missing one sub-slot. In this case,
    skip_overflow_last_ss_validation must be set to True. This will skip validation of end of slots, sub-epochs,
    and lead to other small tweaks in validation.
    """
    # 1. Check that the previous block exists in the blockchain, or that it is correct

    prev_sb = sub_blocks.try_sub_block(header_block.prev_header_hash)
    genesis_block = prev_sb is None
    if genesis_block and header_block.prev_header_hash != constants.GENESIS_CHALLENGE:
        return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH)

    overflow = is_overflow_sub_block(
        constants, header_block.reward_chain_sub_block.signage_point_index)
    if skip_overflow_last_ss_validation and overflow:
        finished_sub_slots_since_prev = len(
            header_block.finished_sub_slots) + 1
    else:
        finished_sub_slots_since_prev = len(header_block.finished_sub_slots)

    new_sub_slot: bool = finished_sub_slots_since_prev > 0

    can_finish_se: bool = False
    can_finish_epoch: bool = False
    if genesis_block:
        height: uint32 = uint32(0)
        assert expected_difficulty == constants.DIFFICULTY_STARTING
        assert expected_sub_slot_iters == constants.SUB_SLOT_ITERS_STARTING
    else:
        assert prev_sb is not None
        height = uint32(prev_sb.height + 1)
        if prev_sb.sub_epoch_summary_included is not None:
            can_finish_se, can_finish_epoch = False, False
        else:
            if new_sub_slot:
                can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch(
                    constants,
                    prev_sb.height,
                    prev_sb.deficit,
                    sub_blocks,
                    prev_sb.prev_hash,
                    False,
                )
            else:
                can_finish_se = False
                can_finish_epoch = False

    # 2. Check finished slots that have been crossed since prev_sb
    ses_hash: Optional[bytes32] = None
    if new_sub_slot and not skip_overflow_last_ss_validation:
        # Finished a slot(s) since previous block. The first sub-slot must have at least one sub-block, and all
        # subsequent sub-slots must be empty
        for finished_sub_slot_n, sub_slot in enumerate(
                header_block.finished_sub_slots):
            # Start of slot challenge is fetched from SP
            challenge_hash: bytes32 = sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.challenge

            if finished_sub_slot_n == 0:
                if genesis_block:
                    # 2a. check sub-slot challenge hash for genesis block
                    if challenge_hash != constants.GENESIS_CHALLENGE:
                        return None, ValidationError(
                            Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
                else:
                    assert prev_sb is not None
                    curr: SubBlockRecord = prev_sb
                    while not curr.first_in_sub_slot:
                        curr = sub_blocks.sub_block_record(curr.prev_hash)
                    assert curr.finished_challenge_slot_hashes is not None

                    # 2b. check sub-slot challenge hash for non-genesis block
                    if not curr.finished_challenge_slot_hashes[
                            -1] == challenge_hash:
                        print(curr.finished_challenge_slot_hashes[-1],
                              challenge_hash)
                        return None, ValidationError(
                            Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
            else:
                # 2c. check sub-slot challenge hash for empty slot
                if (not header_block.finished_sub_slots[
                        finished_sub_slot_n - 1].challenge_chain.get_hash()
                        == challenge_hash):
                    return None, ValidationError(
                        Err.INVALID_PREV_CHALLENGE_SLOT_HASH)

            if genesis_block:
                # 2d. Validate that genesis block has no ICC
                if sub_slot.infused_challenge_chain is not None:
                    return None, ValidationError(Err.SHOULD_NOT_HAVE_ICC)
            else:
                assert prev_sb is not None
                icc_iters_committed: Optional[uint64] = None
                icc_iters_proof: Optional[uint64] = None
                icc_challenge_hash: Optional[bytes32] = None
                icc_vdf_input = None
                if prev_sb.deficit < constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
                    # There should be no ICC chain if the last sub block's deficit is 16
                    # Prev sb's deficit is 0, 1, 2, 3, or 4
                    if finished_sub_slot_n == 0:
                        # This is the first sub slot after the last sb, which must have deficit 1-4, and thus an ICC
                        curr = prev_sb
                        while not curr.is_challenge_sub_block(
                                constants) and not curr.first_in_sub_slot:
                            curr = sub_blocks.sub_block_record(curr.prev_hash)
                        if curr.is_challenge_sub_block(constants):
                            icc_challenge_hash = curr.challenge_block_info_hash
                            icc_iters_committed = uint64(
                                prev_sb.sub_slot_iters -
                                curr.ip_iters(constants))
                        else:
                            assert curr.finished_infused_challenge_slot_hashes is not None
                            icc_challenge_hash = curr.finished_infused_challenge_slot_hashes[
                                -1]
                            icc_iters_committed = prev_sb.sub_slot_iters
                        icc_iters_proof = uint64(prev_sb.sub_slot_iters -
                                                 prev_sb.ip_iters(constants))
                        if prev_sb.is_challenge_sub_block(constants):
                            icc_vdf_input = ClassgroupElement.get_default_element(
                            )
                        else:
                            icc_vdf_input = prev_sb.infused_challenge_vdf_output
                    else:
                        # This is not the first sub slot after the last sub block, so we might not have an ICC
                        if (header_block.finished_sub_slots[
                                finished_sub_slot_n - 1].reward_chain.deficit <
                                constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK):
                            finished_ss = header_block.finished_sub_slots[
                                finished_sub_slot_n - 1]
                            assert finished_ss.infused_challenge_chain is not None

                            # Only sets the icc iff the previous sub slots deficit is 4 or less
                            icc_challenge_hash = finished_ss.infused_challenge_chain.get_hash(
                            )
                            icc_iters_committed = prev_sb.sub_slot_iters
                            icc_iters_proof = icc_iters_committed
                            icc_vdf_input = ClassgroupElement.get_default_element(
                            )

                # 2e. Validate that there is not icc iff icc_challenge hash is None
                assert (sub_slot.infused_challenge_chain is
                        None) == (icc_challenge_hash is None)
                if sub_slot.infused_challenge_chain is not None:
                    assert icc_vdf_input is not None
                    assert icc_iters_proof is not None
                    assert icc_challenge_hash is not None
                    assert sub_slot.proofs.infused_challenge_chain_slot_proof is not None
                    # 2f. Check infused challenge chain sub-slot VDF
                    # Only validate from prev_sb to optimize
                    target_vdf_info = VDFInfo(
                        icc_challenge_hash,
                        icc_iters_proof,
                        sub_slot.infused_challenge_chain.
                        infused_challenge_chain_end_of_slot_vdf.output,
                    )
                    if sub_slot.infused_challenge_chain.infused_challenge_chain_end_of_slot_vdf != dataclasses.replace(
                            target_vdf_info,
                            number_of_iterations=icc_iters_committed,
                    ):
                        return None, ValidationError(Err.INVALID_ICC_EOS_VDF)
                    if not skip_vdf_is_valid and not sub_slot.proofs.infused_challenge_chain_slot_proof.is_valid(
                            constants, icc_vdf_input, target_vdf_info, None):
                        return None, ValidationError(Err.INVALID_ICC_EOS_VDF)

                    if sub_slot.reward_chain.deficit == constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
                        # 2g. Check infused challenge sub-slot hash in challenge chain, deficit 16
                        if (sub_slot.infused_challenge_chain.get_hash() !=
                                sub_slot.challenge_chain.
                                infused_challenge_chain_sub_slot_hash):
                            return None, ValidationError(
                                Err.INVALID_ICC_HASH_CC)
                    else:
                        # 2h. Check infused challenge sub-slot hash not included for other deficits
                        if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None:
                            return None, ValidationError(
                                Err.INVALID_ICC_HASH_CC)

                    # 2i. Check infused challenge sub-slot hash in reward sub-slot
                    if (sub_slot.infused_challenge_chain.get_hash() !=
                            sub_slot.reward_chain.
                            infused_challenge_chain_sub_slot_hash):
                        return None, ValidationError(Err.INVALID_ICC_HASH_RC)
                else:
                    # 2j. If no icc, check that the cc doesn't include it
                    if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None:
                        return None, ValidationError(Err.INVALID_ICC_HASH_CC)

                    # 2k. If no icc, check that the cc doesn't include it
                    if sub_slot.reward_chain.infused_challenge_chain_sub_slot_hash is not None:
                        return None, ValidationError(Err.INVALID_ICC_HASH_RC)

            if sub_slot.challenge_chain.subepoch_summary_hash is not None:
                assert ses_hash is None  # Only one of the slots can have it
                ses_hash = sub_slot.challenge_chain.subepoch_summary_hash

            # 2l. check sub-epoch summary hash is None for empty slots
            if finished_sub_slot_n != 0:
                if sub_slot.challenge_chain.subepoch_summary_hash is not None:
                    return None, ValidationError(
                        Err.INVALID_SUB_EPOCH_SUMMARY_HASH)

            if can_finish_epoch and sub_slot.challenge_chain.subepoch_summary_hash is not None:
                # 2m. Check new difficulty and ssi
                if sub_slot.challenge_chain.new_sub_slot_iters != expected_sub_slot_iters:
                    return None, ValidationError(
                        Err.INVALID_NEW_SUB_SLOT_ITERS)
                if sub_slot.challenge_chain.new_difficulty != expected_difficulty:
                    return None, ValidationError(Err.INVALID_NEW_DIFFICULTY)
            else:
                # 2n. Check new difficulty and ssi are None if we don't finish epoch
                if sub_slot.challenge_chain.new_sub_slot_iters is not None:
                    return None, ValidationError(
                        Err.INVALID_NEW_SUB_SLOT_ITERS)
                if sub_slot.challenge_chain.new_difficulty is not None:
                    return None, ValidationError(Err.INVALID_NEW_DIFFICULTY)

            # 2o. Check challenge sub-slot hash in reward sub-slot
            if sub_slot.challenge_chain.get_hash(
            ) != sub_slot.reward_chain.challenge_chain_sub_slot_hash:
                return (
                    None,
                    ValidationError(
                        Err.INVALID_CHALLENGE_SLOT_HASH_RC,
                        "sub-slot hash in reward sub-slot mismatch",
                    ),
                )

            eos_vdf_iters: uint64 = expected_sub_slot_iters
            cc_start_element: ClassgroupElement = ClassgroupElement.get_default_element(
            )
            cc_eos_vdf_challenge: bytes32 = challenge_hash
            if genesis_block:
                if finished_sub_slot_n == 0:
                    # First block, one empty slot. prior_point is the initial challenge
                    rc_eos_vdf_challenge: bytes32 = constants.GENESIS_CHALLENGE
                    cc_eos_vdf_challenge = constants.GENESIS_CHALLENGE
                else:
                    # First block, but have at least two empty slots
                    rc_eos_vdf_challenge = header_block.finished_sub_slots[
                        finished_sub_slot_n - 1].reward_chain.get_hash()
            else:
                assert prev_sb is not None
                if finished_sub_slot_n == 0:
                    # No empty slots, so the starting point of VDF is the last reward block. Uses
                    # the same IPS as the previous block, since it's the same slot
                    rc_eos_vdf_challenge = prev_sb.reward_infusion_new_challenge
                    eos_vdf_iters = uint64(prev_sb.sub_slot_iters -
                                           prev_sb.ip_iters(constants))
                    cc_start_element = prev_sb.challenge_vdf_output
                else:
                    # At least one empty slot, so use previous slot hash. IPS might change because it's a new slot
                    rc_eos_vdf_challenge = header_block.finished_sub_slots[
                        finished_sub_slot_n - 1].reward_chain.get_hash()

            # 2p. Check end of reward slot VDF
            target_vdf_info = VDFInfo(
                rc_eos_vdf_challenge,
                eos_vdf_iters,
                sub_slot.reward_chain.end_of_slot_vdf.output,
            )
            if not skip_vdf_is_valid and not sub_slot.proofs.reward_chain_slot_proof.is_valid(
                    constants,
                    ClassgroupElement.get_default_element(),
                    sub_slot.reward_chain.end_of_slot_vdf,
                    target_vdf_info,
            ):
                return None, ValidationError(Err.INVALID_RC_EOS_VDF)

            # 2q. Check challenge chain sub-slot VDF
            partial_cc_vdf_info = VDFInfo(
                cc_eos_vdf_challenge,
                eos_vdf_iters,
                sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.
                output,
            )
            if genesis_block:
                cc_eos_vdf_info_iters = constants.SUB_SLOT_ITERS_STARTING
            else:
                assert prev_sb is not None
                if finished_sub_slot_n == 0:
                    cc_eos_vdf_info_iters = prev_sb.sub_slot_iters
                else:
                    cc_eos_vdf_info_iters = expected_sub_slot_iters
            # Check that the modified data is correct
            if sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf != dataclasses.replace(
                    partial_cc_vdf_info,
                    number_of_iterations=cc_eos_vdf_info_iters,
            ):
                return None, ValidationError(
                    Err.INVALID_CC_EOS_VDF,
                    "wrong challenge chain end of slot vdf")

            # Pass in None for target info since we are only checking the proof from the temporary point,
            # but the challenge_chain_end_of_slot_vdf actually starts from the start of slot (for light clients)
            if not skip_vdf_is_valid and not sub_slot.proofs.challenge_chain_slot_proof.is_valid(
                    constants, cc_start_element, partial_cc_vdf_info, None):
                return None, ValidationError(Err.INVALID_CC_EOS_VDF)

            if genesis_block:
                # 2r. Check deficit (MIN_SUB.. deficit edge case for genesis block)
                if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
                    return (
                        None,
                        ValidationError(
                            Err.INVALID_DEFICIT,
                            f"genesis, expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}",
                        ),
                    )
            else:
                assert prev_sb is not None
                if prev_sb.deficit == 0:
                    # 2s. If prev sb had deficit 0, resets deficit to MIN_SUB_BLOCK_PER_CHALLENGE_BLOCK
                    if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
                        log.error(
                            constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK, )
                        return (
                            None,
                            ValidationError(
                                Err.INVALID_DEFICIT,
                                f"expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}, saw "
                                f"{sub_slot.reward_chain.deficit}",
                            ),
                        )
                else:
                    # 2t. Otherwise, deficit stays the same at the slot ends, cannot reset until 0
                    if sub_slot.reward_chain.deficit != prev_sb.deficit:
                        return None, ValidationError(
                            Err.INVALID_DEFICIT,
                            "deficit is wrong at slot end")

        # 3. Check sub-epoch summary
        # Note that the subepoch summary is the summary of the previous subepoch (not the one that just finished)
        if not skip_overflow_last_ss_validation:
            if ses_hash is not None:
                # 3a. Check that genesis block does not have sub-epoch summary
                if genesis_block:
                    return (
                        None,
                        ValidationError(
                            Err.INVALID_SUB_EPOCH_SUMMARY_HASH,
                            "genesis with sub-epoch-summary hash",
                        ),
                    )
                assert prev_sb is not None

                # 3b. Check that we finished a slot and we finished a sub-epoch
                if not new_sub_slot or not can_finish_se:
                    return (
                        None,
                        ValidationError(
                            Err.INVALID_SUB_EPOCH_SUMMARY_HASH,
                            f"new sub-slot: {new_sub_slot} finishes sub-epoch {can_finish_se}",
                        ),
                    )

                # 3c. Check the actual sub-epoch is correct
                expected_sub_epoch_summary = make_sub_epoch_summary(
                    constants,
                    sub_blocks,
                    height,
                    sub_blocks.sub_block_record(prev_sb.prev_hash),
                    expected_difficulty if can_finish_epoch else None,
                    expected_sub_slot_iters if can_finish_epoch else None,
                )
                expected_hash = expected_sub_epoch_summary.get_hash()
                if expected_hash != ses_hash:
                    log.error(f"{expected_sub_epoch_summary}")
                    return (
                        None,
                        ValidationError(
                            Err.INVALID_SUB_EPOCH_SUMMARY,
                            f"expected ses hash: {expected_hash} got {ses_hash} ",
                        ),
                    )
            elif new_sub_slot and not genesis_block:
                # 3d. Check that we don't have to include a sub-epoch summary
                if can_finish_se or can_finish_epoch:
                    return (
                        None,
                        ValidationError(
                            Err.INVALID_SUB_EPOCH_SUMMARY,
                            "block finishes sub-epoch but ses-hash is None",
                        ),
                    )

    # 4. Check if the number of sub-blocks is less than the max
    if not new_sub_slot and not genesis_block:
        assert prev_sb is not None
        num_sub_blocks = 2  # This includes the current sub-block and the prev sub-block
        curr = prev_sb
        while not curr.first_in_sub_slot:
            num_sub_blocks += 1
            curr = sub_blocks.sub_block_record(curr.prev_hash)
        if num_sub_blocks > constants.MAX_SUB_SLOT_SUB_BLOCKS:
            return None, ValidationError(Err.TOO_MANY_SUB_BLOCKS)

    # If sub_block state is correct, we should always find a challenge here
    # This computes what the challenge should be for this sub-block

    challenge = get_block_challenge(
        constants,
        header_block,
        sub_blocks,
        genesis_block,
        overflow,
        skip_overflow_last_ss_validation,
    )

    # 5a. Check proof of space
    if challenge != header_block.reward_chain_sub_block.pos_ss_cc_challenge_hash:
        log.error(f"Finished slots: {header_block.finished_sub_slots}")
        log.error(
            f"Data: {genesis_block} {overflow} {skip_overflow_last_ss_validation} {header_block.total_iters} "
            f"{header_block.reward_chain_sub_block.signage_point_index}"
            f"Prev: {prev_sb}")
        log.error(
            f"Challenge {challenge} provided {header_block.reward_chain_sub_block.pos_ss_cc_challenge_hash}"
        )
        return None, ValidationError(Err.INVALID_CC_CHALLENGE)

    # 5b. Check proof of space
    if header_block.reward_chain_sub_block.challenge_chain_sp_vdf is None:
        # Edge case of first sp (start of slot), where sp_iters == 0
        cc_sp_hash: bytes32 = challenge
    else:
        cc_sp_hash = header_block.reward_chain_sub_block.challenge_chain_sp_vdf.output.get_hash(
        )

    q_str: Optional[
        bytes32] = header_block.reward_chain_sub_block.proof_of_space.verify_and_get_quality_string(
            constants, challenge, cc_sp_hash)
    if q_str is None:
        return None, ValidationError(Err.INVALID_POSPACE)

    # 6. check signage point index
    # no need to check negative values as this is uint 8
    if header_block.reward_chain_sub_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT:
        return None, ValidationError(Err.INVALID_SP_INDEX)

    # Note that required iters might be from the previous slot (if we are in an overflow sub-block)
    required_iters: uint64 = calculate_iterations_quality(
        q_str,
        header_block.reward_chain_sub_block.proof_of_space.size,
        expected_difficulty,
        cc_sp_hash,
    )

    # 7. check signage point index
    # no need to check negative values as this is uint8. (Assumes types are checked)
    if header_block.reward_chain_sub_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT:
        return None, ValidationError(Err.INVALID_SP_INDEX)

    # 8a. check signage point index 0 has no cc sp
    if (header_block.reward_chain_sub_block.signage_point_index == 0) != (
            header_block.reward_chain_sub_block.challenge_chain_sp_vdf is
            None):
        return None, ValidationError(Err.INVALID_SP_INDEX)

    # 8b. check signage point index 0 has no rc sp
    if (header_block.reward_chain_sub_block.signage_point_index == 0) != (
            header_block.reward_chain_sub_block.reward_chain_sp_vdf is None):
        return None, ValidationError(Err.INVALID_SP_INDEX)

    sp_iters: uint64 = calculate_sp_iters(
        constants,
        expected_sub_slot_iters,
        header_block.reward_chain_sub_block.signage_point_index,
    )

    ip_iters: uint64 = calculate_ip_iters(
        constants,
        expected_sub_slot_iters,
        header_block.reward_chain_sub_block.signage_point_index,
        required_iters,
    )
    if header_block.reward_chain_sub_block.challenge_chain_sp_vdf is None:
        # Blocks with very low required iters are not overflow blocks
        assert not overflow

    # 9. Check no overflows in the first sub-slot of a new epoch
    # (although they are OK in the second sub-slot), this is important
    if overflow and can_finish_epoch:
        if finished_sub_slots_since_prev < 2:
            return None, ValidationError(
                Err.NO_OVERFLOWS_IN_FIRST_SUB_SLOT_NEW_EPOCH)

    # 10. Check total iters
    if genesis_block:
        total_iters: uint128 = uint128(expected_sub_slot_iters *
                                       finished_sub_slots_since_prev)
    else:
        assert prev_sb is not None
        if new_sub_slot:
            total_iters = prev_sb.total_iters
            # Add the rest of the slot of prev_sb
            total_iters = uint128(total_iters + prev_sb.sub_slot_iters -
                                  prev_sb.ip_iters(constants))
            # Add other empty slots
            total_iters = uint128(total_iters +
                                  (expected_sub_slot_iters *
                                   (finished_sub_slots_since_prev - 1)))
        else:
            # Slot iters is guaranteed to be the same for header_block and prev_sb
            # This takes the beginning of the slot, and adds ip_iters
            total_iters = uint128(prev_sb.total_iters -
                                  prev_sb.ip_iters(constants))
    total_iters = uint128(total_iters + ip_iters)
    if total_iters != header_block.reward_chain_sub_block.total_iters:
        return (
            None,
            ValidationError(
                Err.INVALID_TOTAL_ITERS,
                f"expected {total_iters} got {header_block.reward_chain_sub_block.total_iters}",
            ),
        )

    sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - (
        expected_sub_slot_iters if overflow else 0))
    if overflow and skip_overflow_last_ss_validation:
        dummy_vdf_info = VDFInfo(
            bytes32([0] * 32),
            uint64(1),
            ClassgroupElement.get_default_element(),
        )
        dummy_sub_slot = EndOfSubSlotBundle(
            ChallengeChainSubSlot(dummy_vdf_info, None, None, None, None),
            None,
            RewardChainSubSlot(dummy_vdf_info, bytes32([0] * 32), None,
                               uint8(0)),
            SubSlotProofs(VDFProof(uint8(0), b""), None,
                          VDFProof(uint8(0), b"")),
        )
        sub_slots_to_pass_in = header_block.finished_sub_slots + [
            dummy_sub_slot
        ]
    else:
        sub_slots_to_pass_in = header_block.finished_sub_slots
    (
        cc_vdf_challenge,
        rc_vdf_challenge,
        cc_vdf_input,
        rc_vdf_input,
        cc_vdf_iters,
        rc_vdf_iters,
    ) = get_signage_point_vdf_info(
        constants,
        sub_slots_to_pass_in,
        overflow,
        prev_sb,
        sub_blocks,
        sp_total_iters,
        sp_iters,
    )

    # 11. Check reward chain sp proof
    if sp_iters != 0:
        assert (header_block.reward_chain_sub_block.reward_chain_sp_vdf
                is not None and header_block.reward_chain_sp_proof is not None)
        target_vdf_info = VDFInfo(
            rc_vdf_challenge,
            rc_vdf_iters,
            header_block.reward_chain_sub_block.reward_chain_sp_vdf.output,
        )
        if not skip_vdf_is_valid and not header_block.reward_chain_sp_proof.is_valid(
                constants,
                rc_vdf_input,
                header_block.reward_chain_sub_block.reward_chain_sp_vdf,
                target_vdf_info,
        ):
            return None, ValidationError(Err.INVALID_RC_SP_VDF)
        rc_sp_hash = header_block.reward_chain_sub_block.reward_chain_sp_vdf.output.get_hash(
        )
    else:
        # Edge case of first sp (start of slot), where sp_iters == 0
        assert overflow is not None
        if header_block.reward_chain_sub_block.reward_chain_sp_vdf is not None:
            return None, ValidationError(Err.INVALID_RC_SP_VDF)
        if new_sub_slot:
            rc_sp_hash = header_block.finished_sub_slots[
                -1].reward_chain.get_hash()
        else:
            if genesis_block:
                rc_sp_hash = constants.GENESIS_CHALLENGE
            else:
                assert prev_sb is not None
                curr = prev_sb
                while not curr.first_in_sub_slot:
                    curr = sub_blocks.sub_block_record(curr.prev_hash)
                assert curr.finished_reward_slot_hashes is not None
                rc_sp_hash = curr.finished_reward_slot_hashes[-1]

    # 12. Check reward chain sp signature
    if not AugSchemeMPL.verify(
            header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
            rc_sp_hash,
            header_block.reward_chain_sub_block.reward_chain_sp_signature,
    ):
        return None, ValidationError(Err.INVALID_RC_SIGNATURE)

    # 13. Check cc sp vdf
    if sp_iters != 0:
        assert header_block.reward_chain_sub_block.challenge_chain_sp_vdf is not None
        assert header_block.challenge_chain_sp_proof is not None
        target_vdf_info = VDFInfo(
            cc_vdf_challenge,
            cc_vdf_iters,
            header_block.reward_chain_sub_block.challenge_chain_sp_vdf.output,
        )

        if header_block.reward_chain_sub_block.challenge_chain_sp_vdf != dataclasses.replace(
                target_vdf_info,
                number_of_iterations=sp_iters,
        ):
            return None, ValidationError(Err.INVALID_CC_SP_VDF)
        if not skip_vdf_is_valid and not header_block.challenge_chain_sp_proof.is_valid(
                constants, cc_vdf_input, target_vdf_info, None):
            return None, ValidationError(Err.INVALID_CC_SP_VDF)
    else:
        assert overflow is not None
        if header_block.reward_chain_sub_block.challenge_chain_sp_vdf is not None:
            return None, ValidationError(Err.INVALID_CC_SP_VDF)

    # 14. Check cc sp sig
    if not AugSchemeMPL.verify(
            header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
            cc_sp_hash,
            header_block.reward_chain_sub_block.challenge_chain_sp_signature,
    ):
        return None, ValidationError(Err.INVALID_CC_SIGNATURE,
                                     "invalid cc sp sig")

    # 15. Check is_block
    if genesis_block:
        if header_block.foliage_sub_block.foliage_block_hash is None:
            return None, ValidationError(Err.INVALID_IS_BLOCK,
                                         "invalid genesis")
    else:
        assert prev_sb is not None
        # Finds the previous block
        curr = prev_sb
        while not curr.is_block:
            curr = sub_blocks.sub_block_record(curr.prev_hash)

        # The first sub-block to have an sp > the last block's infusion iters, is a block
        if overflow:
            our_sp_total_iters: uint128 = uint128(total_iters - ip_iters +
                                                  sp_iters -
                                                  expected_sub_slot_iters)
        else:
            our_sp_total_iters = uint128(total_iters - ip_iters + sp_iters)
        if (our_sp_total_iters > curr.total_iters) != (
                header_block.foliage_sub_block.foliage_block_hash is not None):
            return None, ValidationError(Err.INVALID_IS_BLOCK)
        if (our_sp_total_iters > curr.total_iters) != (
                header_block.foliage_sub_block.foliage_block_signature
                is not None):
            return None, ValidationError(Err.INVALID_IS_BLOCK)

    # 16. Check foliage sub block signature by plot key
    if not AugSchemeMPL.verify(
            header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
            header_block.foliage_sub_block.foliage_sub_block_data.get_hash(),
            header_block.foliage_sub_block.foliage_sub_block_signature,
    ):
        return None, ValidationError(Err.INVALID_PLOT_SIGNATURE)

    # 17. Check foliage block signature by plot key
    if header_block.foliage_sub_block.foliage_block_hash is not None:
        if not AugSchemeMPL.verify(
                header_block.reward_chain_sub_block.proof_of_space.
                plot_public_key,
                header_block.foliage_sub_block.foliage_block_hash,
                header_block.foliage_sub_block.foliage_block_signature,
        ):
            return None, ValidationError(Err.INVALID_PLOT_SIGNATURE)

    # 18. Check unfinished reward chain sub block hash
    if (header_block.reward_chain_sub_block.get_hash() !=
            header_block.foliage_sub_block.foliage_sub_block_data.
            unfinished_reward_block_hash):
        return None, ValidationError(Err.INVALID_URSB_HASH)

    # 19. Check pool target max height
    if (header_block.foliage_sub_block.foliage_sub_block_data.pool_target.
            max_height != 0 and header_block.foliage_sub_block.
            foliage_sub_block_data.pool_target.max_height < height):
        return None, ValidationError(Err.OLD_POOL_TARGET)

    # 20a. Check pre-farm puzzle hashes for genesis sub-block.
    if genesis_block:
        if (header_block.foliage_sub_block.foliage_sub_block_data.pool_target.
                puzzle_hash != constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH):
            log.error(
                f"Pool target {header_block.foliage_sub_block.foliage_sub_block_data.pool_target} hb {header_block}"
            )
            return None, ValidationError(Err.INVALID_PREFARM)
        if (header_block.foliage_sub_block.foliage_sub_block_data.
                farmer_reward_puzzle_hash !=
                constants.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH):
            return None, ValidationError(Err.INVALID_PREFARM)
    else:
        # 20b. Check pool target signature. Should not check this for genesis sub-block.
        if not AugSchemeMPL.verify(
                header_block.reward_chain_sub_block.proof_of_space.
                pool_public_key,
                bytes(header_block.foliage_sub_block.foliage_sub_block_data.
                      pool_target),
                header_block.foliage_sub_block.foliage_sub_block_data.
                pool_signature,
        ):
            return None, ValidationError(Err.INVALID_POOL_SIGNATURE)

    # 21. Check extension data if applicable. None for mainnet.
    # 22. Check if foliage block is present
    if (header_block.foliage_sub_block.foliage_block_hash
            is not None) != (header_block.foliage_block is not None):
        return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE)

    if (header_block.foliage_sub_block.foliage_block_signature
            is not None) != (header_block.foliage_block is not None):
        return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE)

    if header_block.foliage_block is not None:
        # 23. Check foliage block hash
        if header_block.foliage_block.get_hash(
        ) != header_block.foliage_sub_block.foliage_block_hash:
            return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH)

        if genesis_block:
            # 24a. Check prev block hash for genesis
            if header_block.foliage_block.prev_block_hash != constants.GENESIS_CHALLENGE:
                return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH)
        else:
            assert prev_sb is not None
            # 24b. Check prev block hash for non-genesis
            curr_sb: SubBlockRecord = prev_sb
            while not curr_sb.is_block:
                curr_sb = sub_blocks.sub_block_record(curr_sb.prev_hash)
            if not header_block.foliage_block.prev_block_hash == curr_sb.header_hash:
                log.error(
                    f"Prev BH: {header_block.foliage_block.prev_block_hash} {curr_sb.header_hash} curr sb: {curr_sb}"
                )
                return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH)

        # 25. The filter hash in the Foliage Block must be the hash of the filter
        if check_filter:
            if header_block.foliage_block.filter_hash != std_hash(
                    header_block.transactions_filter):
                return None, ValidationError(
                    Err.INVALID_TRANSACTIONS_FILTER_HASH)

        # 26. The timestamp in Foliage Block must comply with the timestamp rules
        if prev_sb is not None:
            last_timestamps: List[uint64] = []
            curr_sb = sub_blocks.sub_block_record(
                header_block.foliage_block.prev_block_hash)
            assert curr_sb.timestamp is not None
            while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS:
                last_timestamps.append(curr_sb.timestamp)
                fetched: Optional[SubBlockRecord] = sub_blocks.try_sub_block(
                    curr_sb.prev_block_hash)
                if not fetched:
                    break
                curr_sb = fetched
            if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS:
                # For blocks 1 to 10, average timestamps of all previous blocks
                assert curr_sb.height == 0
            prev_time: uint64 = uint64(
                int(sum(last_timestamps) // len(last_timestamps)))
            if header_block.foliage_block.timestamp <= prev_time:
                return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_PAST)
            if header_block.foliage_block.timestamp > int(
                    time.time() + constants.MAX_FUTURE_TIME):
                return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_FUTURE)

    return required_iters, None  # Valid unfinished header block
def next_sub_epoch_summary(
    constants: ConsensusConstants,
    sub_blocks: BlockchainInterface,
    required_iters: uint64,
    block: Union[UnfinishedBlock, FullBlock],
    can_finish_soon: bool = False,
) -> Optional[SubEpochSummary]:
    """
    Returns the sub-epoch summary that can be included in the sub-block after block. If it should include one. Block
    must be eligible to be the last sub-block in the epoch. If not, returns None. Assumes that there is a new slot
    ending after block.

    Args:
        constants: consensus constants being used for this chain
        sub_blocks: interface to cached SBR
        required_iters: required iters of the proof of space in block
        block: the (potentially) last sub-block in the new epoch
        can_finish_soon: this is useful when sending SES to timelords. We might not be able to finish it, but we will
            soon (within MAX_SUB_SLOT_SUB_BLOCKS)

    Returns:
        object: the new sub-epoch summary
    """
    signage_point_index = block.reward_chain_sub_block.signage_point_index
    prev_sb: Optional[SubBlockRecord] = sub_blocks.try_sub_block(block.prev_header_hash)
    if prev_sb is None or prev_sb.height == 0:
        return None

    if len(block.finished_sub_slots) > 0 and block.finished_sub_slots[0].challenge_chain.new_difficulty is not None:
        # We just included a sub-epoch summary
        return None

    assert prev_sb is not None
    # This is the ssi of the current block
    sub_slot_iters = get_next_sub_slot_iters(
        constants,
        sub_blocks,
        prev_sb.prev_hash,
        prev_sb.height,
        prev_sb.sub_slot_iters,
        prev_sb.deficit,
        len(block.finished_sub_slots) > 0,
        prev_sb.sp_total_iters(constants),
    )
    overflow = is_overflow_sub_block(constants, signage_point_index)
    deficit = calculate_deficit(
        constants,
        uint32(prev_sb.height + 1),
        prev_sb,
        overflow,
        len(block.finished_sub_slots),
    )
    can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch(
        constants,
        uint32(prev_sb.height + 1),
        deficit,
        sub_blocks,
        prev_sb.header_hash if prev_sb is not None else None,
        can_finish_soon,
    )

    # can't finish se, no summary
    if not can_finish_se:
        return None

    next_difficulty = None
    next_sub_slot_iters = None

    # if can finish epoch, new difficulty and ssi
    if can_finish_epoch:
        sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index)
        ip_iters = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters)
        next_difficulty = get_next_difficulty(
            constants,
            sub_blocks,
            block.prev_header_hash,
            uint32(prev_sb.height + 1),
            uint64(prev_sb.weight - sub_blocks.sub_block_record(prev_sb.prev_hash).weight),
            deficit,
            True,
            uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
            True,
        )
        next_sub_slot_iters = get_next_sub_slot_iters(
            constants,
            sub_blocks,
            block.prev_header_hash,
            uint32(prev_sb.height + 1),
            sub_slot_iters,
            deficit,
            True,
            uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
            True,
        )

    return make_sub_epoch_summary(
        constants,
        sub_blocks,
        uint32(prev_sb.height + 2),
        prev_sb,
        next_difficulty,
        next_sub_slot_iters,
    )