Exemplo n.º 1
0
def find_fork_point_in_chain(
    blocks: BlockchainInterface,
    block_1: Union[BlockRecord, HeaderBlock],
    block_2: Union[BlockRecord, HeaderBlock],
) -> int:
    """Tries to find height where new chain (block_2) diverged from block_1 (assuming prev blocks
    are all included in chain)
    Returns -1 if chains have no common ancestor
    * assumes the fork point is loaded in blocks
    """
    while block_2.height > 0 or block_1.height > 0:
        if block_2.height > block_1.height:
            block_2 = blocks.block_record(block_2.prev_hash)
        elif block_1.height > block_2.height:
            block_1 = blocks.block_record(block_1.prev_hash)
        else:
            if block_2.header_hash == block_1.header_hash:
                return block_2.height
            block_2 = blocks.block_record(block_2.prev_hash)
            block_1 = blocks.block_record(block_1.prev_hash)
    if block_2 != block_1:
        # All blocks are different
        return -1

    # First block is the same
    return 0
Exemplo n.º 2
0
def get_block_challenge(
    constants: ConsensusConstants,
    header_block: Union[UnfinishedHeaderBlock, UnfinishedBlock, HeaderBlock,
                        FullBlock],
    blocks: BlockchainInterface,
    genesis_block: bool,
    overflow: bool,
    skip_overflow_last_ss_validation: bool,
):
    if len(header_block.finished_sub_slots) > 0:
        if overflow:
            # New sub-slot with overflow block
            if skip_overflow_last_ss_validation:
                # In this case, we are missing the final sub-slot bundle (it's not finished yet), however
                # There is a whole empty slot before this block is infused
                challenge: bytes32 = header_block.finished_sub_slots[
                    -1].challenge_chain.get_hash()
            else:
                challenge = header_block.finished_sub_slots[
                    -1].challenge_chain.challenge_chain_end_of_slot_vdf.challenge
        else:
            # No overflow, new slot with a new challenge
            challenge = header_block.finished_sub_slots[
                -1].challenge_chain.get_hash()
    else:
        if genesis_block:
            challenge = constants.GENESIS_CHALLENGE
        else:
            if overflow:
                if skip_overflow_last_ss_validation:
                    # Overflow infusion without the new slot, so get the last challenge
                    challenges_to_look_for = 1
                else:
                    # Overflow infusion, so get the second to last challenge. skip_overflow_last_ss_validation is False,
                    # Which means no sub slots are omitted
                    challenges_to_look_for = 2
            else:
                challenges_to_look_for = 1
            reversed_challenge_hashes: List[bytes32] = []
            curr: BlockRecord = blocks.block_record(
                header_block.prev_header_hash)
            while len(reversed_challenge_hashes) < challenges_to_look_for:
                if curr.first_in_sub_slot:
                    assert curr.finished_challenge_slot_hashes is not None
                    reversed_challenge_hashes += reversed(
                        curr.finished_challenge_slot_hashes)
                if curr.height == 0:
                    assert curr.finished_challenge_slot_hashes is not None
                    assert len(curr.finished_challenge_slot_hashes) > 0
                    break
                curr = blocks.block_record(curr.prev_hash)
            challenge = reversed_challenge_hashes[challenges_to_look_for - 1]
    return challenge
def can_finish_sub_and_full_epoch(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    height: uint32,
    prev_header_hash: Optional[bytes32],
    deficit: uint8,
    block_at_height_included_ses: bool,
) -> Tuple[bool, bool]:
    """
    Returns a bool tuple
    first bool is true if the next sub-slot after height will form part of a new sub-epoch. Therefore
    block height is the last block, and height + 1 is in a new sub-epoch.
    second bool is true if the next sub-slot after height will form part of a new sub-epoch and epoch.
    Therefore, block height is the last block, and height + 1 is in a new epoch.

    Args:
        constants: consensus constants being used for this chain
        blocks: dictionary from header hash to SBR of all included SBR
        height: block height of the (potentially) last block in the sub-epoch
        prev_header_hash: prev_header hash of the block at height, assuming not genesis
        deficit: deficit of block at height height
        block_at_height_included_ses: whether or not the block at height height already included a SES
    """

    if height < constants.SUB_EPOCH_BLOCKS - 1:
        return False, False

    assert prev_header_hash is not None

    if deficit > 0:
        return False, False

    if block_at_height_included_ses:
        # If we just included a sub_epoch_summary, we cannot include one again
        return False, False

    # This does not check the two edge cases where (height + 1) % constants.SUB_EPOCH_BLOCKS is 0 or 1
    # If it's 0, height+1 is the first place that a sub-epoch can be included
    # If it's 1, we just checked whether 0 included it in the previous check
    if (height + 1) % constants.SUB_EPOCH_BLOCKS > 1:
        curr: BlockRecord = blocks.block_record(prev_header_hash)
        while curr.height % constants.SUB_EPOCH_BLOCKS > 0:
            if curr.sub_epoch_summary_included is not None:
                return False, False
            curr = blocks.block_record(curr.prev_hash)

        if curr.sub_epoch_summary_included is not None:
            return False, False

    # For checking new epoch, make sure the epoch blocks are aligned
    return True, height_can_be_first_in_epoch(constants, uint32(height + 1))
def get_next_sub_slot_iters_and_difficulty(
    constants: ConsensusConstants,
    is_first_in_sub_slot: bool,
    prev_b: Optional[BlockRecord],
    blocks: BlockchainInterface,
) -> Tuple[uint64, uint64]:
    """
    Retrieves the current sub_slot iters and difficulty of the next block after prev_b.

    Args:
        constants: consensus constants being used for this chain
        is_first_in_sub_slot: Whether the next block is the first in the sub slot
        prev_b: the previous block (last block in the epoch)
        blocks: dictionary from header hash to SBR of all included SBR

    """

    # genesis
    if prev_b is None:
        return constants.SUB_SLOT_ITERS_STARTING, constants.DIFFICULTY_STARTING

    if prev_b.height != 0:
        prev_difficulty: uint64 = uint64(
            prev_b.weight - blocks.block_record(prev_b.prev_hash).weight)
    else:
        # prev block is genesis
        prev_difficulty = uint64(prev_b.weight)

    if prev_b.sub_epoch_summary_included is not None:
        return prev_b.sub_slot_iters, prev_difficulty

    sp_total_iters = prev_b.sp_total_iters(constants)
    difficulty: uint64 = _get_next_difficulty(
        constants,
        blocks,
        prev_b.prev_hash,
        prev_b.height,
        prev_difficulty,
        prev_b.deficit,
        False,  # Already checked above
        is_first_in_sub_slot,
        sp_total_iters,
    )

    sub_slot_iters: uint64 = _get_next_sub_slot_iters(
        constants,
        blocks,
        prev_b.prev_hash,
        prev_b.height,
        prev_b.sub_slot_iters,
        prev_b.deficit,
        False,  # Already checked above
        is_first_in_sub_slot,
        sp_total_iters,
    )

    return sub_slot_iters, difficulty
Exemplo n.º 5
0
async def wallet_next_block_check(
    peer: WSChiaConnection, potential_peek: uint32, blockchain: BlockchainInterface
) -> bool:
    block_response = await peer.request_header_blocks(
        wallet_protocol.RequestHeaderBlocks(potential_peek, potential_peek)
    )
    if block_response is not None and isinstance(block_response, wallet_protocol.RespondHeaderBlocks):
        our_peak = blockchain.get_peak()
        if our_peak is not None and block_response.header_blocks[0].prev_header_hash == our_peak.header_hash:
            return True
    return False
Exemplo n.º 6
0
async def check_fork_next_block(
    blockchain: BlockchainInterface, fork_point_height: uint32, peers_with_peak: List, check_block_future: Callable
):
    our_peak_height = blockchain.get_peak_height()
    ses_heigths = blockchain.get_ses_heights()
    if len(ses_heigths) > 2 and our_peak_height is not None:
        ses_heigths.sort()
        max_fork_ses_height = ses_heigths[-3]
        potential_peek = uint32(our_peak_height + 1)
        # This is the fork point in SES in the case where no fork was detected
        if blockchain.get_peak_height() is not None and fork_point_height == max_fork_ses_height:
            for peer in peers_with_peak:
                if peer.closed:
                    peers_with_peak.remove(peer)
                    continue
                # Grab a block at peak + 1 and check if fork point is actually our current height
                if await check_block_future(peer, potential_peek, blockchain):
                    fork_point_height = our_peak_height
                    break
    return fork_point_height
def _get_blocks_at_height(
        blocks: BlockchainInterface,
        prev_b: BlockRecord,
        target_height: uint32,
        max_num_blocks: uint32 = uint32(1),
) -> List[BlockRecord]:
    """
    Return a consecutive list of BlockRecords starting at target_height, returning a maximum of
    max_num_blocks. Assumes all block records are present. Does a slot linear search, if the blocks are not
    in the path of the peak. Can only fetch ancestors of prev_b.

    Args:
        blocks: dict from header hash to BlockRecord.
        prev_b: prev_b (to start backwards search).
        target_height: target block to start
        max_num_blocks: max number of blocks to fetch (although less might be fetched)

    """
    if blocks.contains_height(prev_b.height):
        header_hash = blocks.height_to_hash(prev_b.height)
        if header_hash == prev_b.header_hash:
            # Efficient fetching, since we are fetching ancestor blocks within the heaviest chain. We can directly
            # use the height_to_block_record method
            block_list: List[BlockRecord] = []
            for h in range(target_height, target_height + max_num_blocks):
                assert blocks.contains_height(uint32(h))
                block_list.append(blocks.height_to_block_record(uint32(h)))
            return block_list

    # Slow fetching, goes back one by one, since we are in a fork
    curr_b: BlockRecord = prev_b
    target_blocks = []
    while curr_b.height >= target_height:
        if curr_b.height < target_height + max_num_blocks:
            target_blocks.append(curr_b)
        if curr_b.height == 0:
            break
        curr_b = blocks.block_record(curr_b.prev_hash)
    return list(reversed(target_blocks))
Exemplo n.º 8
0
def get_prev_transaction_block(
    curr: BlockRecord,
    blocks: BlockchainInterface,
    total_iters_sp: uint128,
) -> Tuple[bool, BlockRecord]:
    prev_transaction_block = curr
    while not curr.is_transaction_block:
        curr = blocks.block_record(curr.prev_hash)
    if total_iters_sp > curr.total_iters:
        prev_transaction_block = curr
        is_transaction_block = True
    else:
        is_transaction_block = False
    return is_transaction_block, prev_transaction_block
Exemplo n.º 9
0
def final_eos_is_already_included(
    header_block: Union[UnfinishedHeaderBlock, UnfinishedBlock, HeaderBlock,
                        FullBlock],
    blocks: BlockchainInterface,
    sub_slot_iters: uint64,
) -> bool:
    """
    Args:
        header_block: An overflow block, with potentially missing information about the new sub slot
        blocks: all blocks that have been included before header_block
        sub_slot_iters: sub_slot_iters at the header_block

    Returns: True iff the missing sub slot was already included in a previous block. Returns False if the sub
    slot was not included yet, and therefore it is the responsibility of this block to include it

    """
    if len(header_block.finished_sub_slots) > 0:
        # We already have an included empty sub slot, which means the prev block is 2 sub slots behind.
        return False
    curr: BlockRecord = blocks.block_record(header_block.prev_header_hash)

    # We also check if curr is close to header_block, which means it's in the same sub slot
    seen_overflow_block = curr.overflow and (
        header_block.total_iters - curr.total_iters < sub_slot_iters // 2)
    while not curr.first_in_sub_slot and not curr.height == 0:
        if curr.overflow and header_block.total_iters - curr.total_iters < sub_slot_iters // 2:
            seen_overflow_block = True
        curr = blocks.block_record(curr.prev_hash)

    if curr.first_in_sub_slot and seen_overflow_block:
        # We have seen another overflow block in this slot (same as header_block), therefore there are no
        # missing sub slots
        return True

    # We have not seen any overflow blocks, therefore header_block will have to include the missing sub slot in
    # the future
    return False
Exemplo n.º 10
0
    def get_finished_sub_slots(
        self,
        block_records: BlockchainInterface,
        prev_b: Optional[BlockRecord],
        last_challenge_to_add: bytes32,
    ) -> Optional[List[EndOfSubSlotBundle]]:
        """
        Retrieves the EndOfSubSlotBundles that are in the store either:
        1. From the starting challenge if prev_b is None
        2. That are not included in the blockchain with peak of prev_b if prev_b is not None

        Stops at last_challenge
        """

        if prev_b is None:
            # The first sub slot must be None
            assert self.finished_sub_slots[0][0] is None
            challenge_in_chain: bytes32 = self.constants.GENESIS_CHALLENGE
        else:
            curr: BlockRecord = prev_b
            while not curr.first_in_sub_slot:
                curr = block_records.block_record(curr.prev_hash)
            assert curr is not None
            assert curr.finished_challenge_slot_hashes is not None
            challenge_in_chain = curr.finished_challenge_slot_hashes[-1]

        if last_challenge_to_add == challenge_in_chain:
            # No additional slots to add
            return []

        collected_sub_slots: List[EndOfSubSlotBundle] = []
        found_last_challenge = False
        found_connecting_challenge = False
        for sub_slot, sps, total_iters in self.finished_sub_slots[1:]:
            assert sub_slot is not None
            if sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.challenge == challenge_in_chain:
                found_connecting_challenge = True
            if found_connecting_challenge:
                collected_sub_slots.append(sub_slot)
            if found_connecting_challenge and sub_slot.challenge_chain.get_hash(
            ) == last_challenge_to_add:
                found_last_challenge = True
                break
        if not found_last_challenge:
            log.warning(
                f"Did not find hash {last_challenge_to_add} connected to "
                f"{challenge_in_chain}")
            return None
        return collected_sub_slots
def make_sub_epoch_summary(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    blocks_included_height: uint32,
    prev_prev_block: BlockRecord,
    new_difficulty: Optional[uint64],
    new_sub_slot_iters: Optional[uint64],
) -> SubEpochSummary:
    """
    Creates a sub-epoch-summary object, assuming that the first block in the new sub-epoch is at height
    "blocks_included_height". Prev_prev_b is the second to last block in the previous sub-epoch. On a new epoch,
    new_difficulty and new_sub_slot_iters are also added.

    Args:
        constants: consensus constants being used for this chain
        blocks: dictionary from header hash to SBR of all included SBR
        blocks_included_height: block height in which the SES will be included
        prev_prev_block: second to last block in epoch
        new_difficulty: difficulty in new epoch
        new_sub_slot_iters: sub slot iters in new epoch
    """
    assert prev_prev_block.height == blocks_included_height - 2
    # First sub_epoch
    # This is not technically because more blocks can potentially be included than 2*MAX_SUB_SLOT_BLOCKS,
    # But assuming less than 128 overflow blocks get infused in the first 2 slots, it's not an issue
    if (blocks_included_height +
            constants.MAX_SUB_SLOT_BLOCKS) // constants.SUB_EPOCH_BLOCKS <= 1:
        return SubEpochSummary(
            constants.GENESIS_CHALLENGE,
            constants.GENESIS_CHALLENGE,
            uint8(0),
            None,
            None,
        )
    curr: BlockRecord = prev_prev_block
    while curr.sub_epoch_summary_included is None:
        curr = blocks.block_record(curr.prev_hash)
    assert curr is not None
    assert curr.finished_reward_slot_hashes is not None
    prev_ses = curr.sub_epoch_summary_included.get_hash()
    return SubEpochSummary(
        prev_ses,
        curr.finished_reward_slot_hashes[-1],
        uint8(curr.height % constants.SUB_EPOCH_BLOCKS),
        new_difficulty,
        new_sub_slot_iters,
    )
def _get_next_sub_slot_iters(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    prev_header_hash: bytes32,
    height: uint32,
    curr_sub_slot_iters: uint64,
    deficit: uint8,
    block_at_height_included_ses: bool,
    new_slot: bool,
    signage_point_total_iters: uint128,
    skip_epoch_check=False,
) -> uint64:
    """
    Returns the slot iterations required for the next block after the one at height, where new_slot is true
    iff the next block will be in the next slot. WARNING: assumes that the block at height is not the first block
    in a sub-epoch.

    Args:
        constants: consensus constants being used for this chain
        blocks: dictionary from header hash to SBR of all included SBR
        prev_header_hash: header hash of the previous block
        height: the block height of the block to look at
        curr_sub_slot_iters: sub-slot iters at the infusion point of the block at height
        deficit: deficit of block at height height
        new_slot: whether or not there is a new slot after height
        signage_point_total_iters: signage point iters of the block at height
        skip_epoch_check: don't check correct epoch
    """
    next_height: uint32 = uint32(height + 1)

    if next_height < constants.EPOCH_BLOCKS:
        return uint64(constants.SUB_SLOT_ITERS_STARTING)

    if not blocks.contains_block(prev_header_hash):
        raise ValueError(f"Header hash {prev_header_hash} not in blocks")

    prev_b: BlockRecord = blocks.block_record(prev_header_hash)

    # If we are in the same epoch, return same ssi
    if not skip_epoch_check:
        _, can_finish_epoch = can_finish_sub_and_full_epoch(
            constants, blocks, height, prev_header_hash, deficit,
            block_at_height_included_ses)
        if not new_slot or not can_finish_epoch:
            return curr_sub_slot_iters

    last_block_prev: BlockRecord = _get_second_to_last_transaction_block_in_previous_epoch(
        constants, blocks, prev_b)

    # This gets the last transaction block before this block's signage point. Assuming the block at height height
    # is the last block infused in the epoch: If this block ends up being a
    # transaction block, then last_block_curr will be the second to last tx block in the epoch. If this block
    # is not a transaction block, that means there was exactly one other tx block included in between our signage
    # point and infusion point, and therefore last_block_curr is the second to last as well.
    last_block_curr = prev_b
    while last_block_curr.total_iters > signage_point_total_iters or not last_block_curr.is_transaction_block:
        last_block_curr = blocks.block_record(last_block_curr.prev_hash)
    assert last_block_curr.timestamp is not None and last_block_prev.timestamp is not None

    # This is computed as the iterations per second in last epoch, times the target number of seconds per slot
    new_ssi_precise: uint64 = uint64(
        constants.SUB_SLOT_TIME_TARGET *
        (last_block_curr.total_iters - last_block_prev.total_iters) //
        (last_block_curr.timestamp - last_block_prev.timestamp))

    # Only change by a max factor as a sanity check
    max_ssi = uint64(constants.DIFFICULTY_CHANGE_MAX_FACTOR *
                     last_block_curr.sub_slot_iters)
    min_ssi = uint64(last_block_curr.sub_slot_iters //
                     constants.DIFFICULTY_CHANGE_MAX_FACTOR)
    if new_ssi_precise >= last_block_curr.sub_slot_iters:
        new_ssi_precise = uint64(min(new_ssi_precise, max_ssi))
    else:
        new_ssi_precise = uint64(
            max([constants.NUM_SPS_SUB_SLOT, new_ssi_precise, min_ssi]))

    new_ssi = truncate_to_significant_bits(new_ssi_precise,
                                           constants.SIGNIFICANT_BITS)
    new_ssi = uint64(
        new_ssi -
        new_ssi % constants.NUM_SPS_SUB_SLOT)  # Must divide the sub slot
    assert count_significant_bits(new_ssi) <= constants.SIGNIFICANT_BITS
    return new_ssi
Exemplo n.º 13
0
    def new_signage_point(
        self,
        index: uint8,
        blocks: BlockchainInterface,
        peak: Optional[BlockRecord],
        next_sub_slot_iters: uint64,
        signage_point: SignagePoint,
        skip_vdf_validation=False,
    ) -> bool:
        """
        Returns true if sp successfully added
        """
        assert len(self.finished_sub_slots) >= 1

        if peak is None or peak.height < 2:
            sub_slot_iters = self.constants.SUB_SLOT_ITERS_STARTING
        else:
            sub_slot_iters = peak.sub_slot_iters

        # If we don't have this slot, return False
        if index == 0 or index >= self.constants.NUM_SPS_SUB_SLOT:
            return False
        assert (signage_point.cc_vdf is not None
                and signage_point.cc_proof is not None
                and signage_point.rc_vdf is not None
                and signage_point.rc_proof is not None)
        for sub_slot, sp_arr, start_ss_total_iters in self.finished_sub_slots:
            if sub_slot is None:
                assert start_ss_total_iters == 0
                ss_challenge_hash = self.constants.GENESIS_CHALLENGE
                ss_reward_hash = self.constants.GENESIS_CHALLENGE
            else:
                ss_challenge_hash = sub_slot.challenge_chain.get_hash()
                ss_reward_hash = sub_slot.reward_chain.get_hash()
            if ss_challenge_hash == signage_point.cc_vdf.challenge:
                # If we do have this slot, find the Prev block from SP and validate SP
                if peak is not None and start_ss_total_iters > peak.total_iters:
                    # We are in a future sub slot from the peak, so maybe there is a new SSI
                    checkpoint_size: uint64 = uint64(
                        next_sub_slot_iters // self.constants.NUM_SPS_SUB_SLOT)
                    delta_iters: uint64 = uint64(checkpoint_size * index)
                    future_sub_slot: bool = True
                else:
                    # We are not in a future sub slot from the peak, so there is no new SSI
                    checkpoint_size = uint64(sub_slot_iters //
                                             self.constants.NUM_SPS_SUB_SLOT)
                    delta_iters = uint64(checkpoint_size * index)
                    future_sub_slot = False
                sp_total_iters = start_ss_total_iters + delta_iters

                curr = peak
                if peak is None or future_sub_slot:
                    check_from_start_of_ss = True
                else:
                    check_from_start_of_ss = False
                    while (curr is not None
                           and curr.total_iters > start_ss_total_iters
                           and curr.total_iters > sp_total_iters):
                        if curr.first_in_sub_slot:
                            # Did not find a block where it's iters are before our sp_total_iters, in this ss
                            check_from_start_of_ss = True
                            break
                        curr = blocks.block_record(curr.prev_hash)

                if check_from_start_of_ss:
                    # Check VDFs from start of sub slot
                    cc_vdf_info_expected = VDFInfo(
                        ss_challenge_hash,
                        delta_iters,
                        signage_point.cc_vdf.output,
                    )

                    rc_vdf_info_expected = VDFInfo(
                        ss_reward_hash,
                        delta_iters,
                        signage_point.rc_vdf.output,
                    )
                else:
                    # Check VDFs from curr
                    assert curr is not None
                    cc_vdf_info_expected = VDFInfo(
                        ss_challenge_hash,
                        uint64(sp_total_iters - curr.total_iters),
                        signage_point.cc_vdf.output,
                    )
                    rc_vdf_info_expected = VDFInfo(
                        curr.reward_infusion_new_challenge,
                        uint64(sp_total_iters - curr.total_iters),
                        signage_point.rc_vdf.output,
                    )
                if not signage_point.cc_vdf == dataclasses.replace(
                        cc_vdf_info_expected,
                        number_of_iterations=delta_iters):
                    self.add_to_future_sp(signage_point, index)
                    return False
                if check_from_start_of_ss:
                    start_ele = ClassgroupElement.get_default_element()
                else:
                    assert curr is not None
                    start_ele = curr.challenge_vdf_output
                if not skip_vdf_validation:
                    if not signage_point.cc_proof.normalized_to_identity and not signage_point.cc_proof.is_valid(
                            self.constants,
                            start_ele,
                            cc_vdf_info_expected,
                    ):
                        self.add_to_future_sp(signage_point, index)
                        return False
                    if signage_point.cc_proof.normalized_to_identity and not signage_point.cc_proof.is_valid(
                            self.constants,
                            ClassgroupElement.get_default_element(),
                            signage_point.cc_vdf,
                    ):
                        self.add_to_future_sp(signage_point, index)
                        return False

                if rc_vdf_info_expected.challenge != signage_point.rc_vdf.challenge:
                    # This signage point is probably outdated
                    self.add_to_future_sp(signage_point, index)
                    return False

                if not skip_vdf_validation:
                    if not signage_point.rc_proof.is_valid(
                            self.constants,
                            ClassgroupElement.get_default_element(),
                            signage_point.rc_vdf,
                            rc_vdf_info_expected,
                    ):
                        self.add_to_future_sp(signage_point, index)
                        return False

                sp_arr[index] = signage_point
                return True
        self.add_to_future_sp(signage_point, index)
        return False
Exemplo n.º 14
0
    def new_finished_sub_slot(
        self,
        eos: EndOfSubSlotBundle,
        blocks: BlockchainInterface,
        peak: Optional[BlockRecord],
        peak_full_block: Optional[FullBlock],
    ) -> Optional[List[timelord_protocol.NewInfusionPointVDF]]:
        """
        Returns false if not added. Returns a list if added. The list contains all infusion points that depended
        on this sub slot
        """
        assert len(self.finished_sub_slots) >= 1
        assert (peak is None) == (peak_full_block is None)

        last_slot, _, last_slot_iters = self.finished_sub_slots[-1]

        cc_challenge: bytes32 = (last_slot.challenge_chain.get_hash()
                                 if last_slot is not None else
                                 self.constants.GENESIS_CHALLENGE)
        rc_challenge: bytes32 = (last_slot.reward_chain.get_hash()
                                 if last_slot is not None else
                                 self.constants.GENESIS_CHALLENGE)
        icc_challenge: Optional[bytes32] = None
        icc_iters: Optional[uint64] = None

        # Skip if already present
        for slot, _, _ in self.finished_sub_slots:
            if slot == eos:
                return []

        if eos.challenge_chain.challenge_chain_end_of_slot_vdf.challenge != cc_challenge:
            # This slot does not append to our next slot
            # This prevent other peers from appending fake VDFs to our cache
            return None

        if peak is None:
            sub_slot_iters = self.constants.SUB_SLOT_ITERS_STARTING
        else:
            sub_slot_iters = peak.sub_slot_iters

        total_iters = uint128(last_slot_iters + sub_slot_iters)

        if peak is not None and peak.total_iters > last_slot_iters:
            # Peak is in this slot
            rc_challenge = eos.reward_chain.end_of_slot_vdf.challenge
            cc_start_element = peak.challenge_vdf_output
            iters = uint64(total_iters - peak.total_iters)
            if peak.reward_infusion_new_challenge != rc_challenge:
                # We don't have this challenge hash yet
                if rc_challenge not in self.future_eos_cache:
                    self.future_eos_cache[rc_challenge] = []
                self.future_eos_cache[rc_challenge].append(eos)
                self.future_cache_key_times[rc_challenge] = int(time.time())
                log.info(
                    f"Don't have challenge hash {rc_challenge}, caching EOS")
                return None

            if peak.deficit == self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK:
                icc_start_element = None
            elif peak.deficit == self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK - 1:
                icc_start_element = ClassgroupElement.get_default_element()
            else:
                icc_start_element = peak.infused_challenge_vdf_output

            if peak.deficit < self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK:
                curr = peak
                while not curr.first_in_sub_slot and not curr.is_challenge_block(
                        self.constants):
                    curr = blocks.block_record(curr.prev_hash)
                if curr.is_challenge_block(self.constants):
                    icc_challenge = curr.challenge_block_info_hash
                    icc_iters = uint64(total_iters - curr.total_iters)
                else:
                    assert curr.finished_infused_challenge_slot_hashes is not None
                    icc_challenge = curr.finished_infused_challenge_slot_hashes[
                        -1]
                    icc_iters = sub_slot_iters
                assert icc_challenge is not None

            if can_finish_sub_and_full_epoch(
                    self.constants,
                    blocks,
                    peak.height,
                    peak.prev_hash,
                    peak.deficit,
                    peak.sub_epoch_summary_included is not None,
            )[0]:
                assert peak_full_block is not None
                ses: Optional[SubEpochSummary] = next_sub_epoch_summary(
                    self.constants, blocks, peak.required_iters,
                    peak_full_block, True)
                if ses is not None:
                    if eos.challenge_chain.subepoch_summary_hash != ses.get_hash(
                    ):
                        log.warning(
                            f"SES not correct {ses.get_hash(), eos.challenge_chain}"
                        )
                        return None
                else:
                    if eos.challenge_chain.subepoch_summary_hash is not None:
                        log.warning("SES not correct, should be None")
                        return None
        else:
            # This is on an empty slot
            cc_start_element = ClassgroupElement.get_default_element()
            icc_start_element = ClassgroupElement.get_default_element()
            iters = sub_slot_iters
            icc_iters = sub_slot_iters

            # The icc should only be present if the previous slot had an icc too, and not deficit 0 (just finished slot)
            icc_challenge = (last_slot.infused_challenge_chain.get_hash()
                             if last_slot is not None
                             and last_slot.infused_challenge_chain is not None
                             and last_slot.reward_chain.deficit !=
                             self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK else
                             None)

        # Validate cc VDF
        partial_cc_vdf_info = VDFInfo(
            cc_challenge,
            iters,
            eos.challenge_chain.challenge_chain_end_of_slot_vdf.output,
        )
        # The EOS will have the whole sub-slot iters, but the proof is only the delta, from the last peak
        if eos.challenge_chain.challenge_chain_end_of_slot_vdf != dataclasses.replace(
                partial_cc_vdf_info,
                number_of_iterations=sub_slot_iters,
        ):
            return None
        if (not eos.proofs.challenge_chain_slot_proof.normalized_to_identity
                and not eos.proofs.challenge_chain_slot_proof.is_valid(
                    self.constants,
                    cc_start_element,
                    partial_cc_vdf_info,
                )):
            return None
        if (eos.proofs.challenge_chain_slot_proof.normalized_to_identity
                and not eos.proofs.challenge_chain_slot_proof.is_valid(
                    self.constants,
                    ClassgroupElement.get_default_element(),
                    eos.challenge_chain.challenge_chain_end_of_slot_vdf,
                )):
            return None

        # Validate reward chain VDF
        if not eos.proofs.reward_chain_slot_proof.is_valid(
                self.constants,
                ClassgroupElement.get_default_element(),
                eos.reward_chain.end_of_slot_vdf,
                VDFInfo(rc_challenge, iters,
                        eos.reward_chain.end_of_slot_vdf.output),
        ):
            return None

        if icc_challenge is not None:
            assert icc_start_element is not None
            assert icc_iters is not None
            assert eos.infused_challenge_chain is not None
            assert eos.infused_challenge_chain is not None
            assert eos.proofs.infused_challenge_chain_slot_proof is not None

            partial_icc_vdf_info = VDFInfo(
                icc_challenge,
                iters,
                eos.infused_challenge_chain.
                infused_challenge_chain_end_of_slot_vdf.output,
            )
            # The EOS will have the whole sub-slot iters, but the proof is only the delta, from the last peak
            if eos.infused_challenge_chain.infused_challenge_chain_end_of_slot_vdf != dataclasses.replace(
                    partial_icc_vdf_info,
                    number_of_iterations=icc_iters,
            ):
                return None
            if (not eos.proofs.infused_challenge_chain_slot_proof.
                    normalized_to_identity and
                    not eos.proofs.infused_challenge_chain_slot_proof.is_valid(
                        self.constants, icc_start_element,
                        partial_icc_vdf_info)):
                return None
            if (eos.proofs.infused_challenge_chain_slot_proof.
                    normalized_to_identity and
                    not eos.proofs.infused_challenge_chain_slot_proof.is_valid(
                        self.constants,
                        ClassgroupElement.get_default_element(),
                        eos.infused_challenge_chain.
                        infused_challenge_chain_end_of_slot_vdf,
                    )):
                return None
        else:
            # This is the first sub slot and it's empty, therefore there is no ICC
            if eos.infused_challenge_chain is not None or eos.proofs.infused_challenge_chain_slot_proof is not None:
                return None

        self.finished_sub_slots.append(
            (eos, [None] * self.constants.NUM_SPS_SUB_SLOT, total_iters))

        new_ips: List[timelord_protocol.NewInfusionPointVDF] = []
        for ip in self.future_ip_cache.get(eos.reward_chain.get_hash(), []):
            new_ips.append(ip)

        return new_ips
Exemplo n.º 15
0
async def validate_block_body(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    block_store: BlockStore,
    coin_store: CoinStore,
    peak: Optional[BlockRecord],
    block: Union[FullBlock, UnfinishedBlock],
    height: uint32,
    npc_result: Optional[NPCResult],
    fork_point_with_peak: Optional[uint32],
    get_block_generator: Callable,
    validate_signature=True,
) -> Tuple[Optional[Err], Optional[NPCResult]]:
    """
    This assumes the header block has been completely validated.
    Validates the transactions and body of the block. Returns None for the first value if everything
    validates correctly, or an Err if something does not validate. For the second value, returns a CostResult
    only if validation succeeded, and there are transactions. In other cases it returns None. The NPC result is
    the result of running the generator with the previous generators refs. It is only present for transaction
    blocks which have spent coins.
    """
    if isinstance(block, FullBlock):
        assert height == block.height
    prev_transaction_block_height: uint32 = uint32(0)

    # 1. For non transaction-blocs: foliage block, transaction filter, transactions info, and generator must
    # be empty. If it is a block but not a transaction block, there is no body to validate. Check that all fields are
    # None
    if block.foliage.foliage_transaction_block_hash is None:
        if (block.foliage_transaction_block is not None
                or block.transactions_info is not None
                or block.transactions_generator is not None):
            return Err.NOT_BLOCK_BUT_HAS_DATA, None

        prev_tb: BlockRecord = blocks.block_record(block.prev_header_hash)
        while not prev_tb.is_transaction_block:
            prev_tb = blocks.block_record(prev_tb.prev_hash)
        assert prev_tb.timestamp is not None
        if len(block.transactions_generator_ref_list) > 0:
            return Err.NOT_BLOCK_BUT_HAS_DATA, None

        return None, None  # This means the block is valid

    # All checks below this point correspond to transaction blocks
    # 2. For blocks, foliage block, transactions info must not be empty
    if block.foliage_transaction_block is None or block.transactions_info is None:
        return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None
    assert block.foliage_transaction_block is not None

    # keeps track of the reward coins that need to be incorporated
    expected_reward_coins: Set[Coin] = set()

    # 3. The transaction info hash in the Foliage block must match the transaction info
    if block.foliage_transaction_block.transactions_info_hash != std_hash(
            block.transactions_info):
        return Err.INVALID_TRANSACTIONS_INFO_HASH, None

    # 4. The foliage block hash in the foliage block must match the foliage block
    if block.foliage.foliage_transaction_block_hash != std_hash(
            block.foliage_transaction_block):
        return Err.INVALID_FOLIAGE_BLOCK_HASH, None

    # 5. The reward claims must be valid for the previous blocks, and current block fees
    # If height == 0, expected_reward_coins will be left empty
    if height > 0:
        # Add reward claims for all blocks from the prev prev block, until the prev block (including the latter)
        prev_transaction_block = blocks.block_record(
            block.foliage_transaction_block.prev_transaction_block_hash)
        prev_transaction_block_height = prev_transaction_block.height
        assert prev_transaction_block.fees is not None
        pool_coin = create_pool_coin(
            prev_transaction_block_height,
            prev_transaction_block.pool_puzzle_hash,
            calculate_pool_reward(prev_transaction_block.height),
            constants.GENESIS_CHALLENGE,
        )
        farmer_coin = create_farmer_coin(
            prev_transaction_block_height,
            prev_transaction_block.farmer_puzzle_hash,
            uint64(
                calculate_base_farmer_reward(prev_transaction_block.height) +
                prev_transaction_block.fees),
            constants.GENESIS_CHALLENGE,
        )
        # Adds the previous block
        expected_reward_coins.add(pool_coin)
        expected_reward_coins.add(farmer_coin)

        # For the second block in the chain, don't go back further
        if prev_transaction_block.height > 0:
            curr_b = blocks.block_record(prev_transaction_block.prev_hash)
            while not curr_b.is_transaction_block:
                expected_reward_coins.add(
                    create_pool_coin(
                        curr_b.height,
                        curr_b.pool_puzzle_hash,
                        calculate_pool_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    ))
                expected_reward_coins.add(
                    create_farmer_coin(
                        curr_b.height,
                        curr_b.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    ))
                curr_b = blocks.block_record(curr_b.prev_hash)

    if set(block.transactions_info.reward_claims_incorporated
           ) != expected_reward_coins:
        return Err.INVALID_REWARD_COINS, None

    if len(block.transactions_info.reward_claims_incorporated) != len(
            expected_reward_coins):
        return Err.INVALID_REWARD_COINS, None

    removals: List[bytes32] = []
    coinbase_additions: List[Coin] = list(expected_reward_coins)
    additions: List[Coin] = []
    npc_list: List[NPC] = []
    removals_puzzle_dic: Dict[bytes32, bytes32] = {}
    cost: uint64 = uint64(0)

    # In header validation we check that timestamp is not more that 10 minutes into the future
    # 6. No transactions before INITIAL_TRANSACTION_FREEZE timestamp
    # (this test has been removed)

    # 7a. The generator root must be the hash of the serialized bytes of
    #     the generator for this block (or zeroes if no generator)
    if block.transactions_generator is not None:
        if std_hash(bytes(block.transactions_generator)
                    ) != block.transactions_info.generator_root:
            return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None
    else:
        if block.transactions_info.generator_root != bytes([0] * 32):
            return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None

    # 8a. The generator_ref_list must be the hash of the serialized bytes of
    #     the generator ref list for this block (or 'one' bytes [0x01] if no generator)
    # 8b. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries
    # 8c. The generator ref list must not point to a height >= this block's height
    if block.transactions_generator_ref_list in (None, []):
        if block.transactions_info.generator_refs_root != bytes([1] * 32):
            return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
    else:
        # If we have a generator reference list, we must have a generator
        if block.transactions_generator is None:
            return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None

        # The generator_refs_root must be the hash of the concatenation of the List[uint32]
        generator_refs_hash = std_hash(b"".join(
            [bytes(i) for i in block.transactions_generator_ref_list]))
        if block.transactions_info.generator_refs_root != generator_refs_hash:
            return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
        if len(block.transactions_generator_ref_list
               ) > constants.MAX_GENERATOR_REF_LIST_SIZE:
            return Err.TOO_MANY_GENERATOR_REFS, None
        if any([
                index >= height
                for index in block.transactions_generator_ref_list
        ]):
            return Err.FUTURE_GENERATOR_REFS, None

    if block.transactions_generator is not None:
        # Get List of names removed, puzzles hashes for removed coins and conditions created

        assert npc_result is not None
        cost = calculate_cost_of_program(block.transactions_generator,
                                         npc_result, constants.COST_PER_BYTE)
        npc_list = npc_result.npc_list

        # 7. Check that cost <= MAX_BLOCK_COST_CLVM
        log.debug(
            f"Cost: {cost} max: {constants.MAX_BLOCK_COST_CLVM} "
            f"percent full: {round(100 * (cost / constants.MAX_BLOCK_COST_CLVM), 2)}%"
        )
        if cost > constants.MAX_BLOCK_COST_CLVM:
            return Err.BLOCK_COST_EXCEEDS_MAX, None

        # 8. The CLVM program must not return any errors
        if npc_result.error is not None:
            return Err(npc_result.error), None

        for npc in npc_list:
            removals.append(npc.coin_name)
            removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash

        additions = additions_for_npc(npc_list)
    else:
        assert npc_result is None

    # 9. Check that the correct cost is in the transactions info
    if block.transactions_info.cost != cost:
        return Err.INVALID_BLOCK_COST, None

    additions_dic: Dict[bytes32, Coin] = {}
    # 10. Check additions for max coin amount
    # Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer
    # We will not even reach here because Coins do type checking (uint64)
    for coin in additions + coinbase_additions:
        additions_dic[coin.name()] = coin
        if coin.amount < 0:
            return Err.COIN_AMOUNT_NEGATIVE, None

        if coin.amount > constants.MAX_COIN_AMOUNT:
            return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

    # 11. Validate addition and removal roots
    root_error = validate_block_merkle_roots(
        block.foliage_transaction_block.additions_root,
        block.foliage_transaction_block.removals_root,
        additions + coinbase_additions,
        removals,
    )
    if root_error:
        return root_error, None

    # 12. The additions and removals must result in the correct filter
    byte_array_tx: List[bytes32] = []

    for coin in additions + coinbase_additions:
        # TODO: address hint error and remove ignore
        #       error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32"
        #       [arg-type]
        byte_array_tx.append(bytearray(
            coin.puzzle_hash))  # type: ignore[arg-type]
    for coin_name in removals:
        # TODO: address hint error and remove ignore
        #       error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32"
        #       [arg-type]
        byte_array_tx.append(bytearray(coin_name))  # type: ignore[arg-type]

    bip158: PyBIP158 = PyBIP158(byte_array_tx)
    encoded_filter = bytes(bip158.GetEncoded())
    filter_hash = std_hash(encoded_filter)

    if filter_hash != block.foliage_transaction_block.filter_hash:
        return Err.INVALID_TRANSACTIONS_FILTER_HASH, None

    # 13. Check for duplicate outputs in additions
    addition_counter = collections.Counter(
        _.name() for _ in additions + coinbase_additions)
    for k, v in addition_counter.items():
        if v > 1:
            return Err.DUPLICATE_OUTPUT, None

    # 14. Check for duplicate spends inside block
    removal_counter = collections.Counter(removals)
    for k, v in removal_counter.items():
        if v > 1:
            return Err.DOUBLE_SPEND, None

    # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block)
    # The fork point is the last block in common between the peak chain and the chain of `block`
    if peak is None or height == 0:
        fork_h: int = -1
    elif fork_point_with_peak is not None:
        fork_h = fork_point_with_peak
    else:
        fork_h = find_fork_point_in_chain(
            blocks, peak, blocks.block_record(block.prev_header_hash))

    # Get additions and removals since (after) fork_h but not including this block
    # The values include: the coin that was added, the height of the block in which it was confirmed, and the
    # timestamp of the block in which it was confirmed
    additions_since_fork: Dict[bytes32, Tuple[Coin, uint32, uint64]] = {
    }  # This includes coinbase additions
    removals_since_fork: Set[bytes32] = set()

    # For height 0, there are no additions and removals before this block, so we can skip
    if height > 0:
        # First, get all the blocks in the fork > fork_h, < block.height
        prev_block: Optional[FullBlock] = await block_store.get_full_block(
            block.prev_header_hash)
        reorg_blocks: Dict[uint32, FullBlock] = {}
        curr: Optional[FullBlock] = prev_block
        assert curr is not None
        while curr.height > fork_h:
            if curr.height == 0:
                break
            curr = await block_store.get_full_block(curr.prev_header_hash)
            assert curr is not None
            reorg_blocks[curr.height] = curr
        if fork_h != -1:
            assert len(reorg_blocks) == height - fork_h - 1

        curr = prev_block
        assert curr is not None
        while curr.height > fork_h:
            # Coin store doesn't contain coins from fork, we have to run generator for each block in fork
            if curr.transactions_generator is not None:
                # These blocks are in the past and therefore assumed to be valid, so get_block_generator won't raise
                curr_block_generator: Optional[
                    BlockGenerator] = await get_block_generator(curr)
                assert curr_block_generator is not None and curr.transactions_info is not None
                curr_npc_result = get_name_puzzle_conditions(
                    curr_block_generator,
                    min(constants.MAX_BLOCK_COST_CLVM,
                        curr.transactions_info.cost),
                    cost_per_byte=constants.COST_PER_BYTE,
                    mempool_mode=False,
                )
                removals_in_curr, additions_in_curr = tx_removals_and_additions(
                    curr_npc_result.npc_list)
            else:
                removals_in_curr = []
                additions_in_curr = []

            for c_name in removals_in_curr:
                assert c_name not in removals_since_fork
                removals_since_fork.add(c_name)
            for c in additions_in_curr:
                assert c.name() not in additions_since_fork
                assert curr.foliage_transaction_block is not None
                additions_since_fork[c.name()] = (
                    c, curr.height, curr.foliage_transaction_block.timestamp)

            for coinbase_coin in curr.get_included_reward_coins():
                assert coinbase_coin.name() not in additions_since_fork
                assert curr.foliage_transaction_block is not None
                additions_since_fork[coinbase_coin.name()] = (
                    coinbase_coin,
                    curr.height,
                    curr.foliage_transaction_block.timestamp,
                )
            if curr.height == 0:
                break
            curr = reorg_blocks[curr.height - 1]
            assert curr is not None

    removal_coin_records: Dict[bytes32, CoinRecord] = {}
    for rem in removals:
        if rem in additions_dic:
            # Ephemeral coin
            rem_coin: Coin = additions_dic[rem]
            new_unspent: CoinRecord = CoinRecord(
                rem_coin,
                height,
                height,
                False,
                block.foliage_transaction_block.timestamp,
            )
            removal_coin_records[new_unspent.name] = new_unspent
        else:
            unspent = await coin_store.get_coin_record(rem)
            if unspent is not None and unspent.confirmed_block_index <= fork_h:
                # Spending something in the current chain, confirmed before fork
                # (We ignore all coins confirmed after fork)
                if unspent.spent == 1 and unspent.spent_block_index <= fork_h:
                    # Check for coins spent in an ancestor block
                    return Err.DOUBLE_SPEND, None
                removal_coin_records[unspent.name] = unspent
            else:
                # This coin is not in the current heaviest chain, so it must be in the fork
                if rem not in additions_since_fork:
                    # Check for spending a coin that does not exist in this fork
                    log.error(
                        f"Err.UNKNOWN_UNSPENT: COIN ID: {rem} NPC RESULT: {npc_result}"
                    )
                    return Err.UNKNOWN_UNSPENT, None
                new_coin, confirmed_height, confirmed_timestamp = additions_since_fork[
                    rem]
                new_coin_record: CoinRecord = CoinRecord(
                    new_coin,
                    confirmed_height,
                    uint32(0),
                    False,
                    confirmed_timestamp,
                )
                removal_coin_records[new_coin_record.name] = new_coin_record

            # This check applies to both coins created before fork (pulled from coin_store),
            # and coins created after fork (additions_since_fork)
            if rem in removals_since_fork:
                # This coin was spent in the fork
                return Err.DOUBLE_SPEND_IN_FORK, None

    removed = 0
    for unspent in removal_coin_records.values():
        removed += unspent.coin.amount

    added = 0
    for coin in additions:
        added += coin.amount

    # 16. Check that the total coin amount for added is <= removed
    if removed < added:
        return Err.MINTING_COIN, None

    fees = removed - added
    assert fees >= 0
    assert_fee_sum: uint128 = uint128(0)

    for npc in npc_list:
        if ConditionOpcode.RESERVE_FEE in npc.condition_dict:
            fee_list: List[ConditionWithArgs] = npc.condition_dict[
                ConditionOpcode.RESERVE_FEE]
            for cvp in fee_list:
                fee = int_from_bytes(cvp.vars[0])
                if fee < 0:
                    return Err.RESERVE_FEE_CONDITION_FAILED, None
                assert_fee_sum = uint128(assert_fee_sum + fee)

    # 17. Check that the assert fee sum <= fees, and that each reserved fee is non-negative
    if fees < assert_fee_sum:
        return Err.RESERVE_FEE_CONDITION_FAILED, None

    # 18. Check that the fee amount + farmer reward < maximum coin amount
    if fees + calculate_base_farmer_reward(height) > constants.MAX_COIN_AMOUNT:
        return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

    # 19. Check that the computed fees are equal to the fees in the block header
    if block.transactions_info.fees != fees:
        return Err.INVALID_BLOCK_FEE_AMOUNT, None

    # 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes
    for unspent in removal_coin_records.values():
        if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]:
            return Err.WRONG_PUZZLE_HASH, None

    # 21. Verify conditions
    for npc in npc_list:
        assert height is not None
        unspent = removal_coin_records[npc.coin_name]
        error = mempool_check_conditions_dict(
            unspent,
            npc.condition_dict,
            prev_transaction_block_height,
            block.foliage_transaction_block.timestamp,
        )
        if error:
            return error, None

    # create hash_key list for aggsig check
    pairs_pks, pairs_msgs = pkm_pairs(npc_list,
                                      constants.AGG_SIG_ME_ADDITIONAL_DATA)

    # 22. Verify aggregated signature
    # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster
    if not block.transactions_info.aggregated_signature:
        return Err.BAD_AGGREGATE_SIGNATURE, None

    # The pairing cache is not useful while syncing as each pairing is seen
    # only once, so the extra effort of populating it is not justified.
    # However, we force caching of pairings just for unfinished blocks
    # as the cache is likely to be useful when validating the corresponding
    # finished blocks later.
    if validate_signature:
        force_cache: bool = isinstance(block, UnfinishedBlock)
        if not cached_bls.aggregate_verify(
                pairs_pks, pairs_msgs,
                block.transactions_info.aggregated_signature, force_cache):
            return Err.BAD_AGGREGATE_SIGNATURE, None

    return None, npc_result
Exemplo n.º 16
0
def create_foliage(
    constants: ConsensusConstants,
    reward_block_unfinished: RewardChainBlockUnfinished,
    block_generator: Optional[BlockGenerator],
    aggregate_sig: G2Element,
    additions: List[Coin],
    removals: List[Coin],
    prev_block: Optional[BlockRecord],
    blocks: BlockchainInterface,
    total_iters_sp: uint128,
    timestamp: uint64,
    farmer_reward_puzzlehash: bytes32,
    pool_target: PoolTarget,
    get_plot_signature: Callable[[bytes32, G1Element], G2Element],
    get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]],
    seed: bytes32 = b"",
) -> Tuple[Foliage, Optional[FoliageTransactionBlock], Optional[TransactionsInfo]]:
    """
    Creates a foliage for a given reward chain block. This may or may not be a tx block. In the case of a tx block,
    the return values are not None. This is called at the signage point, so some of this information may be
    tweaked at the infusion point.

    Args:
        constants: consensus constants being used for this chain
        reward_block_unfinished: the reward block to look at, potentially at the signage point
        block_generator: transactions to add to the foliage block, if created
        aggregate_sig: aggregate of all transctions (or infinity element)
        prev_block: the previous block at the signage point
        blocks: dict from header hash to blocks, of all ancestor blocks
        total_iters_sp: total iters at the signage point
        timestamp: timestamp to put into the foliage block
        farmer_reward_puzzlehash: where to pay out farming reward
        pool_target: where to pay out pool reward
        get_plot_signature: retrieve the signature corresponding to the plot public key
        get_pool_signature: retrieve the signature corresponding to the pool public key
        seed: seed to randomize block

    """

    if prev_block is not None:
        res = get_prev_transaction_block(prev_block, blocks, total_iters_sp)
        is_transaction_block: bool = res[0]
        prev_transaction_block: Optional[BlockRecord] = res[1]
    else:
        # Genesis is a transaction block
        prev_transaction_block = None
        is_transaction_block = True

    random.seed(seed)
    # Use the extension data to create different blocks based on header hash
    extension_data: bytes32 = random.randint(0, 100000000).to_bytes(32, "big")
    if prev_block is None:
        height: uint32 = uint32(0)
    else:
        height = uint32(prev_block.height + 1)

    # Create filter
    byte_array_tx: List[bytes32] = []
    tx_additions: List[Coin] = []
    tx_removals: List[bytes32] = []

    pool_target_signature: Optional[G2Element] = get_pool_signature(
        pool_target, reward_block_unfinished.proof_of_space.pool_public_key
    )

    foliage_data = FoliageBlockData(
        reward_block_unfinished.get_hash(),
        pool_target,
        pool_target_signature,
        farmer_reward_puzzlehash,
        extension_data,
    )

    foliage_block_data_signature: G2Element = get_plot_signature(
        foliage_data.get_hash(),
        reward_block_unfinished.proof_of_space.plot_public_key,
    )

    prev_block_hash: bytes32 = constants.GENESIS_CHALLENGE
    if height != 0:
        assert prev_block is not None
        prev_block_hash = prev_block.header_hash

    generator_block_heights_list: List[uint32] = []

    if is_transaction_block:
        cost = uint64(0)

        # Calculate the cost of transactions
        if block_generator is not None:
            generator_block_heights_list = block_generator.block_height_list()
            result: NPCResult = get_name_puzzle_conditions(block_generator, constants.MAX_BLOCK_COST_CLVM, True)
            cost = calculate_cost_of_program(block_generator.program, result, constants.COST_PER_BYTE)

            removal_amount = 0
            addition_amount = 0
            for coin in removals:
                removal_amount += coin.amount
            for coin in additions:
                addition_amount += coin.amount
            spend_bundle_fees = removal_amount - addition_amount
        else:
            spend_bundle_fees = 0

        reward_claims_incorporated = []
        if height > 0:
            assert prev_transaction_block is not None
            assert prev_block is not None
            curr: BlockRecord = prev_block
            while not curr.is_transaction_block:
                curr = blocks.block_record(curr.prev_hash)

            assert curr.fees is not None
            pool_coin = create_pool_coin(
                curr.height, curr.pool_puzzle_hash, calculate_pool_reward(curr.height), constants.GENESIS_CHALLENGE
            )

            farmer_coin = create_farmer_coin(
                curr.height,
                curr.farmer_puzzle_hash,
                uint64(calculate_base_farmer_reward(curr.height) + curr.fees),
                constants.GENESIS_CHALLENGE,
            )
            assert curr.header_hash == prev_transaction_block.header_hash
            reward_claims_incorporated += [pool_coin, farmer_coin]

            if curr.height > 0:
                curr = blocks.block_record(curr.prev_hash)
                # Prev block is not genesis
                while not curr.is_transaction_block:
                    pool_coin = create_pool_coin(
                        curr.height,
                        curr.pool_puzzle_hash,
                        calculate_pool_reward(curr.height),
                        constants.GENESIS_CHALLENGE,
                    )
                    farmer_coin = create_farmer_coin(
                        curr.height,
                        curr.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr.height),
                        constants.GENESIS_CHALLENGE,
                    )
                    reward_claims_incorporated += [pool_coin, farmer_coin]
                    curr = blocks.block_record(curr.prev_hash)
        additions.extend(reward_claims_incorporated.copy())
        for coin in additions:
            tx_additions.append(coin)
            byte_array_tx.append(bytearray(coin.puzzle_hash))
        for coin in removals:
            tx_removals.append(coin.name())
            byte_array_tx.append(bytearray(coin.name()))

        bip158: PyBIP158 = PyBIP158(byte_array_tx)
        encoded = bytes(bip158.GetEncoded())

        removal_merkle_set = MerkleSet()
        addition_merkle_set = MerkleSet()

        # Create removal Merkle set
        for coin_name in tx_removals:
            removal_merkle_set.add_already_hashed(coin_name)

        # Create addition Merkle set
        puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {}

        for coin in tx_additions:
            if coin.puzzle_hash in puzzlehash_coin_map:
                puzzlehash_coin_map[coin.puzzle_hash].append(coin)
            else:
                puzzlehash_coin_map[coin.puzzle_hash] = [coin]

        # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
        for puzzle, coins in puzzlehash_coin_map.items():
            addition_merkle_set.add_already_hashed(puzzle)
            addition_merkle_set.add_already_hashed(hash_coin_list(coins))

        additions_root = addition_merkle_set.get_root()
        removals_root = removal_merkle_set.get_root()

        generator_hash = bytes32([0] * 32)
        if block_generator is not None:
            generator_hash = std_hash(block_generator.program)

        generator_refs_hash = bytes32([1] * 32)
        if generator_block_heights_list not in (None, []):
            generator_ref_list_bytes = b"".join([bytes(i) for i in generator_block_heights_list])
            generator_refs_hash = std_hash(generator_ref_list_bytes)

        filter_hash: bytes32 = std_hash(encoded)

        transactions_info: Optional[TransactionsInfo] = TransactionsInfo(
            generator_hash,
            generator_refs_hash,
            aggregate_sig,
            uint64(spend_bundle_fees),
            cost,
            reward_claims_incorporated,
        )
        if prev_transaction_block is None:
            prev_transaction_block_hash: bytes32 = constants.GENESIS_CHALLENGE
        else:
            prev_transaction_block_hash = prev_transaction_block.header_hash

        assert transactions_info is not None
        foliage_transaction_block: Optional[FoliageTransactionBlock] = FoliageTransactionBlock(
            prev_transaction_block_hash,
            timestamp,
            filter_hash,
            additions_root,
            removals_root,
            transactions_info.get_hash(),
        )
        assert foliage_transaction_block is not None

        foliage_transaction_block_hash: Optional[bytes32] = foliage_transaction_block.get_hash()
        foliage_transaction_block_signature: Optional[G2Element] = get_plot_signature(
            foliage_transaction_block_hash, reward_block_unfinished.proof_of_space.plot_public_key
        )
        assert foliage_transaction_block_signature is not None
    else:
        foliage_transaction_block_hash = None
        foliage_transaction_block_signature = None
        foliage_transaction_block = None
        transactions_info = None
    assert (foliage_transaction_block_hash is None) == (foliage_transaction_block_signature is None)

    foliage = Foliage(
        prev_block_hash,
        reward_block_unfinished.get_hash(),
        foliage_data,
        foliage_block_data_signature,
        foliage_transaction_block_hash,
        foliage_transaction_block_signature,
    )

    return foliage, foliage_transaction_block, transactions_info
Exemplo n.º 17
0
def create_unfinished_block(
    constants: ConsensusConstants,
    sub_slot_start_total_iters: uint128,
    sub_slot_iters: uint64,
    signage_point_index: uint8,
    sp_iters: uint64,
    ip_iters: uint64,
    proof_of_space: ProofOfSpace,
    slot_cc_challenge: bytes32,
    farmer_reward_puzzle_hash: bytes32,
    pool_target: PoolTarget,
    get_plot_signature: Callable[[bytes32, G1Element], G2Element],
    get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]],
    signage_point: SignagePoint,
    timestamp: uint64,
    blocks: BlockchainInterface,
    seed: bytes32 = b"",
    block_generator: Optional[BlockGenerator] = None,
    aggregate_sig: G2Element = G2Element(),
    additions: Optional[List[Coin]] = None,
    removals: Optional[List[Coin]] = None,
    prev_block: Optional[BlockRecord] = None,
    finished_sub_slots_input: List[EndOfSubSlotBundle] = None,
) -> UnfinishedBlock:
    """
    Creates a new unfinished block using all the information available at the signage point. This will have to be
    modified using information from the infusion point.

    Args:
        constants: consensus constants being used for this chain
        sub_slot_start_total_iters: the starting sub-slot iters at the signage point sub-slot
        sub_slot_iters: sub-slot-iters at the infusion point epoch
        signage_point_index: signage point index of the block to create
        sp_iters: sp_iters of the block to create
        ip_iters: ip_iters of the block to create
        proof_of_space: proof of space of the block to create
        slot_cc_challenge: challenge hash at the sp sub-slot
        farmer_reward_puzzle_hash: where to pay out farmer rewards
        pool_target: where to pay out pool rewards
        get_plot_signature: function that returns signature corresponding to plot public key
        get_pool_signature: function that returns signature corresponding to pool public key
        signage_point: signage point information (VDFs)
        timestamp: timestamp to add to the foliage block, if created
        seed: seed to randomize chain
        block_generator: transactions to add to the foliage block, if created
        aggregate_sig: aggregate of all transctions (or infinity element)
        additions: Coins added in spend_bundle
        removals: Coins removed in spend_bundle
        prev_block: previous block (already in chain) from the signage point
        blocks: dictionary from header hash to SBR of all included SBR
        finished_sub_slots_input: finished_sub_slots at the signage point

    Returns:

    """
    if finished_sub_slots_input is None:
        finished_sub_slots: List[EndOfSubSlotBundle] = []
    else:
        finished_sub_slots = finished_sub_slots_input.copy()
    overflow: bool = sp_iters > ip_iters
    total_iters_sp: uint128 = uint128(sub_slot_start_total_iters + sp_iters)
    is_genesis: bool = prev_block is None

    new_sub_slot: bool = len(finished_sub_slots) > 0

    cc_sp_hash: Optional[bytes32] = slot_cc_challenge

    # Only enters this if statement if we are in testing mode (making VDF proofs here)
    if signage_point.cc_vdf is not None:
        assert signage_point.rc_vdf is not None
        cc_sp_hash = signage_point.cc_vdf.output.get_hash()
        rc_sp_hash = signage_point.rc_vdf.output.get_hash()
    else:
        if new_sub_slot:
            rc_sp_hash = finished_sub_slots[-1].reward_chain.get_hash()
        else:
            if is_genesis:
                rc_sp_hash = constants.GENESIS_CHALLENGE
            else:
                assert prev_block is not None
                assert blocks is not None
                curr = prev_block
                while not curr.first_in_sub_slot:
                    curr = blocks.block_record(curr.prev_hash)
                assert curr.finished_reward_slot_hashes is not None
                rc_sp_hash = curr.finished_reward_slot_hashes[-1]
        signage_point = SignagePoint(None, None, None, None)

    cc_sp_signature: Optional[G2Element] = get_plot_signature(cc_sp_hash, proof_of_space.plot_public_key)
    rc_sp_signature: Optional[G2Element] = get_plot_signature(rc_sp_hash, proof_of_space.plot_public_key)
    assert cc_sp_signature is not None
    assert rc_sp_signature is not None
    assert blspy.AugSchemeMPL.verify(proof_of_space.plot_public_key, cc_sp_hash, cc_sp_signature)

    total_iters = uint128(sub_slot_start_total_iters + ip_iters + (sub_slot_iters if overflow else 0))

    rc_block = RewardChainBlockUnfinished(
        total_iters,
        signage_point_index,
        slot_cc_challenge,
        proof_of_space,
        signage_point.cc_vdf,
        cc_sp_signature,
        signage_point.rc_vdf,
        rc_sp_signature,
    )
    if additions is None:
        additions = []
    if removals is None:
        removals = []
    (foliage, foliage_transaction_block, transactions_info,) = create_foliage(
        constants,
        rc_block,
        block_generator,
        aggregate_sig,
        additions,
        removals,
        prev_block,
        blocks,
        total_iters_sp,
        timestamp,
        farmer_reward_puzzle_hash,
        pool_target,
        get_plot_signature,
        get_pool_signature,
        seed,
    )
    return UnfinishedBlock(
        finished_sub_slots,
        rc_block,
        signage_point.cc_proof,
        signage_point.rc_proof,
        foliage,
        foliage_transaction_block,
        transactions_info,
        block_generator.program if block_generator else None,
        block_generator.block_height_list() if block_generator else [],
    )
def _get_second_to_last_transaction_block_in_previous_epoch(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    last_b: BlockRecord,
) -> BlockRecord:
    """
    Retrieves the second to last transaction block in the previous epoch.

    Args:
        constants: consensus constants being used for this chain
        blocks: dict from header hash to block of all relevant blocks
        last_b: last-block in the current epoch, or last block we have seen, if potentially finishing epoch soon

           prev epoch surpassed  prev epoch started                  epoch sur.  epoch started
            v                       v                                v         v
      |.B...B....B. B....B...|......B....B.....B...B.|.B.B.B..|..B...B.B.B...|.B.B.B. B.|........
            PREV EPOCH                 CURR EPOCH                               NEW EPOCH

     The blocks selected for the timestamps are the second to last transaction blocks in each epoch.
     Block at height 0 is an exception. Note that H mod EPOCH_BLOCKS where H is the height of the first block in the
     epoch, must be >= 0, and < 128.
    """

    # This height is guaranteed to be in the next epoch (even when last_b is not actually the last block)
    height_in_next_epoch = (last_b.height + 2 * constants.MAX_SUB_SLOT_BLOCKS +
                            constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK + 5)
    height_epoch_surpass: uint32 = uint32(height_in_next_epoch -
                                          (height_in_next_epoch %
                                           constants.EPOCH_BLOCKS))
    height_prev_epoch_surpass: uint32 = uint32(height_epoch_surpass -
                                               constants.EPOCH_BLOCKS)

    assert height_prev_epoch_surpass % constants.EPOCH_BLOCKS == height_prev_epoch_surpass % constants.EPOCH_BLOCKS == 0

    # Sanity check, don't go too far past epoch barrier
    assert (height_in_next_epoch -
            height_epoch_surpass) < (5 * constants.MAX_SUB_SLOT_BLOCKS)

    if height_prev_epoch_surpass == 0:
        # The genesis block is an edge case, where we measure from the first block in epoch (height 0), as opposed to
        # a block in the previous epoch, which would be height < 0
        return _get_blocks_at_height(blocks, last_b, uint32(0))[0]

    # If the prev slot is the first slot, the iterations start at 0
    # We will compute the timestamps of the 2nd to last block in epoch, as well as the total iterations at infusion
    prev_slot_start_iters: uint128
    prev_slot_time_start: uint64

    # The target block must be in this range. Either the surpass block must be a transaction block, or something
    # in it's sub slot must be a transaction block. If that is the only transaction block in the sub-slot, the last
    # block in the previous sub-slot from that must also be a transaction block (therefore -1 is used).
    # The max height for the new epoch to start is surpass + 2*MAX_SUB_SLOT_BLOCKS + MIN_BLOCKS_PER_CHALLENGE_BLOCK - 3,
    # since we might have a deficit > 0 when surpass is hit. The +3 is added just in case
    fetched_blocks = _get_blocks_at_height(
        blocks,
        last_b,
        uint32(height_prev_epoch_surpass - constants.MAX_SUB_SLOT_BLOCKS - 1),
        uint32(3 * constants.MAX_SUB_SLOT_BLOCKS +
               constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK + 3),
    )

    # We want to find the last block in the slot at which we surpass the height.
    # The last block in epoch will be before this.
    fetched_index: int = constants.MAX_SUB_SLOT_BLOCKS
    curr_b: BlockRecord = fetched_blocks[fetched_index]
    fetched_index += 1
    assert curr_b.height == height_prev_epoch_surpass - 1
    next_b: BlockRecord = fetched_blocks[fetched_index]
    assert next_b.height == height_prev_epoch_surpass

    # Wait until the slot finishes with a challenge chain infusion at start of slot
    # Note that there are no overflow blocks at the start of new epochs
    while next_b.sub_epoch_summary_included is None:
        curr_b = next_b
        next_b = fetched_blocks[fetched_index]
        fetched_index += 1

    # Backtrack to find the second to last tx block
    found_tx_block = 1 if curr_b.is_transaction_block else 0
    while found_tx_block < 2:
        curr_b = blocks.block_record(curr_b.prev_hash)
        if curr_b.is_transaction_block:
            found_tx_block += 1

    return curr_b
Exemplo n.º 19
0
async def validate_block_body(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    block_store: BlockStore,
    coin_store: CoinStore,
    peak: Optional[BlockRecord],
    block: Union[FullBlock, UnfinishedBlock],
    height: uint32,
    npc_result: Optional[NPCResult],
    fork_point_with_peak: Optional[uint32],
    get_block_generator: Callable,
) -> Tuple[Optional[Err], Optional[NPCResult]]:
    """
    This assumes the header block has been completely validated.
    Validates the transactions and body of the block. Returns None for the first value if everything
    validates correctly, or an Err if something does not validate. For the second value, returns a CostResult
    if validation succeeded, and there are transactions
    """
    if isinstance(block, FullBlock):
        assert height == block.height
    prev_transaction_block_height: uint32 = uint32(0)

    # 1. For non block blocks, foliage block, transaction filter, transactions info, and generator must be empty
    # If it is a block but not a transaction block, there is no body to validate. Check that all fields are None
    if block.foliage.foliage_transaction_block_hash is None:
        if (
            block.foliage_transaction_block is not None
            or block.transactions_info is not None
            or block.transactions_generator is not None
        ):
            return Err.NOT_BLOCK_BUT_HAS_DATA, None
        return None, None  # This means the block is valid

    # 2. For blocks, foliage block, transaction filter, transactions info must not be empty
    if (
        block.foliage_transaction_block is None
        or block.foliage_transaction_block.filter_hash is None
        or block.transactions_info is None
    ):
        return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None

    # keeps track of the reward coins that need to be incorporated
    expected_reward_coins: Set[Coin] = set()

    # 3. The transaction info hash in the Foliage block must match the transaction info
    if block.foliage_transaction_block.transactions_info_hash != std_hash(block.transactions_info):
        return Err.INVALID_TRANSACTIONS_INFO_HASH, None

    # 4. The foliage block hash in the foliage block must match the foliage block
    if block.foliage.foliage_transaction_block_hash != std_hash(block.foliage_transaction_block):
        return Err.INVALID_FOLIAGE_BLOCK_HASH, None

    # 7. The reward claims must be valid for the previous blocks, and current block fees
    if height > 0:
        # Add reward claims for all blocks from the prev prev block, until the prev block (including the latter)
        prev_transaction_block = blocks.block_record(block.foliage_transaction_block.prev_transaction_block_hash)
        prev_transaction_block_height = prev_transaction_block.height
        assert prev_transaction_block.fees is not None
        pool_coin = create_pool_coin(
            prev_transaction_block_height,
            prev_transaction_block.pool_puzzle_hash,
            calculate_pool_reward(prev_transaction_block.height),
            constants.GENESIS_CHALLENGE,
        )
        farmer_coin = create_farmer_coin(
            prev_transaction_block_height,
            prev_transaction_block.farmer_puzzle_hash,
            uint64(calculate_base_farmer_reward(prev_transaction_block.height) + prev_transaction_block.fees),
            constants.GENESIS_CHALLENGE,
        )
        # Adds the previous block
        expected_reward_coins.add(pool_coin)
        expected_reward_coins.add(farmer_coin)

        # For the second block in the chain, don't go back further
        if prev_transaction_block.height > 0:
            curr_b = blocks.block_record(prev_transaction_block.prev_hash)
            while not curr_b.is_transaction_block:
                expected_reward_coins.add(
                    create_pool_coin(
                        curr_b.height,
                        curr_b.pool_puzzle_hash,
                        calculate_pool_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    )
                )
                expected_reward_coins.add(
                    create_farmer_coin(
                        curr_b.height,
                        curr_b.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    )
                )
                curr_b = blocks.block_record(curr_b.prev_hash)

    if set(block.transactions_info.reward_claims_incorporated) != expected_reward_coins:
        return Err.INVALID_REWARD_COINS, None

    removals: List[bytes32] = []
    coinbase_additions: List[Coin] = list(expected_reward_coins)
    additions: List[Coin] = []
    coin_announcement_names: Set[bytes32] = set()
    puzzle_announcement_names: Set[bytes32] = set()
    npc_list: List[NPC] = []
    removals_puzzle_dic: Dict[bytes32, bytes32] = {}
    cost: uint64 = uint64(0)

    if height <= constants.INITIAL_FREEZE_PERIOD and block.transactions_generator is not None:
        return Err.INITIAL_TRANSACTION_FREEZE, None

    if height > constants.INITIAL_FREEZE_PERIOD and constants.NETWORK_TYPE == NetworkType.MAINNET:
        return Err.INITIAL_TRANSACTION_FREEZE, None
    else:
        # 6a. The generator root must be the hash of the serialized bytes of
        #     the generator for this block (or zeroes if no generator)
        if block.transactions_generator is not None:
            if std_hash(bytes(block.transactions_generator)) != block.transactions_info.generator_root:
                return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT, None
        else:
            if block.transactions_info.generator_root != bytes([0] * 32):
                return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT, None

        # 6b. The generator_ref_list must be the hash of the serialized bytes of
        #     the generator ref list for this block (or 'one' bytes [0x01] if no generator)
        # 6c. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries
        if block.transactions_generator_ref_list in (None, []):
            if block.transactions_info.generator_refs_root != bytes([1] * 32):
                return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
        else:
            # If we have a generator reference list, we must have a generator
            if block.transactions_generator is None:
                return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None

            # The generator_refs_root must be the hash of the concatenation of the List[uint32]
            generator_refs_hash = std_hash(b"".join([bytes(i) for i in block.transactions_generator_ref_list]))
            if block.transactions_info.generator_refs_root != generator_refs_hash:
                return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
            if len(block.transactions_generator_ref_list) > constants.MAX_GENERATOR_REF_LIST_SIZE:
                return Err.PRE_SOFT_FORK_TOO_MANY_GENERATOR_REFS, None

        if block.transactions_generator is not None:
            # Get List of names removed, puzzles hashes for removed coins and conditions created

            assert npc_result is not None
            cost = calculate_cost_of_program(block.transactions_generator, npc_result, constants.COST_PER_BYTE)
            npc_list = npc_result.npc_list

            # 8. Check that cost <= MAX_BLOCK_COST_CLVM
            log.warning(f"Cost: {cost} max: {constants.MAX_BLOCK_COST_CLVM}")
            if cost > constants.MAX_BLOCK_COST_CLVM:
                return Err.BLOCK_COST_EXCEEDS_MAX, None
            if npc_result.error is not None:
                return Err.GENERATOR_RUNTIME_ERROR, None

            for npc in npc_list:
                removals.append(npc.coin_name)
                removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash

            additions = additions_for_npc(npc_list)
            coin_announcement_names = coin_announcements_names_for_npc(npc_list)
            puzzle_announcement_names = puzzle_announcements_names_for_npc(npc_list)
        else:
            assert npc_result is None

        # 9. Check that the correct cost is in the transactions info
        if block.transactions_info.cost != cost:
            return Err.INVALID_BLOCK_COST, None

        additions_dic: Dict[bytes32, Coin] = {}
        # 10. Check additions for max coin amount
        # Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer
        for coin in additions + coinbase_additions:
            additions_dic[coin.name()] = coin
            if coin.amount > constants.MAX_COIN_AMOUNT:
                return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

        # 11. Validate addition and removal roots
        root_error = validate_block_merkle_roots(
            block.foliage_transaction_block.additions_root,
            block.foliage_transaction_block.removals_root,
            additions + coinbase_additions,
            removals,
        )
        if root_error:
            return root_error, None

        # 12. The additions and removals must result in the correct filter
        byte_array_tx: List[bytes32] = []

        for coin in additions + coinbase_additions:
            byte_array_tx.append(bytearray(coin.puzzle_hash))
        for coin_name in removals:
            byte_array_tx.append(bytearray(coin_name))

        bip158: PyBIP158 = PyBIP158(byte_array_tx)
        encoded_filter = bytes(bip158.GetEncoded())
        filter_hash = std_hash(encoded_filter)

        if filter_hash != block.foliage_transaction_block.filter_hash:
            return Err.INVALID_TRANSACTIONS_FILTER_HASH, None

        # 13. Check for duplicate outputs in additions
        addition_counter = collections.Counter(_.name() for _ in additions + coinbase_additions)
        for k, v in addition_counter.items():
            if v > 1:
                return Err.DUPLICATE_OUTPUT, None

        # 14. Check for duplicate spends inside block
        removal_counter = collections.Counter(removals)
        for k, v in removal_counter.items():
            if v > 1:
                return Err.DOUBLE_SPEND, None

        # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block)
        if peak is None or height == 0:
            fork_h: int = -1
        elif fork_point_with_peak is not None:
            fork_h = fork_point_with_peak
        else:
            fork_h = find_fork_point_in_chain(blocks, peak, blocks.block_record(block.prev_header_hash))

        if fork_h == -1:
            coin_store_reorg_height = -1
        else:
            last_block_in_common = await blocks.get_block_record_from_db(blocks.height_to_hash(uint32(fork_h)))
            assert last_block_in_common is not None
            coin_store_reorg_height = last_block_in_common.height

        # Get additions and removals since (after) fork_h but not including this block
        additions_since_fork: Dict[bytes32, Tuple[Coin, uint32]] = {}
        removals_since_fork: Set[bytes32] = set()
        coinbases_since_fork: Dict[bytes32, uint32] = {}

        if height > 0:
            prev_block: Optional[FullBlock] = await block_store.get_full_block(block.prev_header_hash)
            reorg_blocks: Dict[int, FullBlock] = {}
            curr: Optional[FullBlock] = prev_block
            assert curr is not None
            reorg_blocks[curr.height] = curr
            while curr.height > fork_h:
                if curr.height == 0:
                    break
                curr = await block_store.get_full_block(curr.prev_header_hash)
                assert curr is not None
                reorg_blocks[curr.height] = curr

            curr = prev_block
            assert curr is not None
            while curr.height > fork_h:
                # Coin store doesn't contain coins from fork, we have to run generator for each block in fork
                if curr.transactions_generator is not None:
                    curr_block_generator: Optional[BlockGenerator] = await get_block_generator(curr)
                    assert curr_block_generator is not None
                    npc_result = get_name_puzzle_conditions(curr_block_generator, False)
                    removals_in_curr, additions_in_curr = tx_removals_and_additions(npc_result.npc_list)
                else:
                    removals_in_curr = []
                    additions_in_curr = []

                for c_name in removals_in_curr:
                    removals_since_fork.add(c_name)
                for c in additions_in_curr:
                    additions_since_fork[c.name()] = (c, curr.height)

                for coinbase_coin in curr.get_included_reward_coins():
                    additions_since_fork[coinbase_coin.name()] = (coinbase_coin, curr.height)
                    coinbases_since_fork[coinbase_coin.name()] = curr.height
                if curr.height == 0:
                    break
                curr = reorg_blocks[curr.height - 1]
                assert curr is not None

        removal_coin_records: Dict[bytes32, CoinRecord] = {}
        for rem in removals:
            if rem in additions_dic:
                # Ephemeral coin
                rem_coin: Coin = additions_dic[rem]
                new_unspent: CoinRecord = CoinRecord(
                    rem_coin,
                    height,
                    uint32(0),
                    False,
                    (rem in coinbases_since_fork),
                    block.foliage_transaction_block.timestamp,
                )
                removal_coin_records[new_unspent.name] = new_unspent
            else:
                unspent = await coin_store.get_coin_record(rem)
                if unspent is not None and unspent.confirmed_block_index <= coin_store_reorg_height:
                    # Spending something in the current chain, confirmed before fork
                    # (We ignore all coins confirmed after fork)
                    if unspent.spent == 1 and unspent.spent_block_index <= coin_store_reorg_height:
                        # Check for coins spent in an ancestor block
                        return Err.DOUBLE_SPEND, None
                    removal_coin_records[unspent.name] = unspent
                else:
                    # This coin is not in the current heaviest chain, so it must be in the fork
                    if rem not in additions_since_fork:
                        # Check for spending a coin that does not exist in this fork
                        # TODO: fix this, there is a consensus bug here
                        return Err.UNKNOWN_UNSPENT, None
                    new_coin, confirmed_height = additions_since_fork[rem]
                    new_coin_record: CoinRecord = CoinRecord(
                        new_coin,
                        confirmed_height,
                        uint32(0),
                        False,
                        (rem in coinbases_since_fork),
                        block.foliage_transaction_block.timestamp,
                    )
                    removal_coin_records[new_coin_record.name] = new_coin_record

                # This check applies to both coins created before fork (pulled from coin_store),
                # and coins created after fork (additions_since_fork)>
                if rem in removals_since_fork:
                    # This coin was spent in the fork
                    return Err.DOUBLE_SPEND, None

        removed = 0
        for unspent in removal_coin_records.values():
            removed += unspent.coin.amount

        added = 0
        for coin in additions:
            added += coin.amount

        # 16. Check that the total coin amount for added is <= removed
        if removed < added:
            return Err.MINTING_COIN, None

        fees = removed - added
        assert_fee_sum: uint64 = uint64(0)

        for npc in npc_list:
            if ConditionOpcode.RESERVE_FEE in npc.condition_dict:
                fee_list: List[ConditionWithArgs] = npc.condition_dict[ConditionOpcode.RESERVE_FEE]
                for cvp in fee_list:
                    fee = int_from_bytes(cvp.vars[0])
                    assert_fee_sum = assert_fee_sum + fee

        # 17. Check that the assert fee sum <= fees
        if fees < assert_fee_sum:
            return Err.RESERVE_FEE_CONDITION_FAILED, None

        # 18. Check that the assert fee amount < maximum coin amount
        if fees > constants.MAX_COIN_AMOUNT:
            return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

        # 19. Check that the computed fees are equal to the fees in the block header
        if block.transactions_info.fees != fees:
            return Err.INVALID_BLOCK_FEE_AMOUNT, None

        # 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes
        for unspent in removal_coin_records.values():
            if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]:
                return Err.WRONG_PUZZLE_HASH, None

        # 21. Verify conditions
        # create hash_key list for aggsig check
        pairs_pks = []
        pairs_msgs = []
        for npc in npc_list:
            assert height is not None
            unspent = removal_coin_records[npc.coin_name]
            error = mempool_check_conditions_dict(
                unspent,
                coin_announcement_names,
                puzzle_announcement_names,
                npc.condition_dict,
                prev_transaction_block_height,
                block.foliage_transaction_block.timestamp,
            )
            if error:
                return error, None
            for pk, m in pkm_pairs_for_conditions_dict(
                npc.condition_dict, npc.coin_name, constants.AGG_SIG_ME_ADDITIONAL_DATA
            ):
                pairs_pks.append(pk)
                pairs_msgs.append(m)

        # 22. Verify aggregated signature
        # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster
        if not block.transactions_info.aggregated_signature:
            return Err.BAD_AGGREGATE_SIGNATURE, None

            # noinspection PyTypeChecker
        if not AugSchemeMPL.aggregate_verify(pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature):
            return Err.BAD_AGGREGATE_SIGNATURE, None

        return None, npc_result
def next_sub_epoch_summary(
    constants: ConsensusConstants,
    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 block after block. If it should include one. Block
    must be eligible to be the last 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
        blocks: interface to cached SBR
        required_iters: required iters of the proof of space in block
        block: the (potentially) last 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_BLOCKS)

    Returns:
        object: the new sub-epoch summary
    """
    signage_point_index = block.reward_chain_block.signage_point_index
    prev_b: Optional[BlockRecord] = blocks.try_block_record(
        block.prev_header_hash)
    if prev_b is None or prev_b.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_b is not None
    # This is the ssi of the current block

    sub_slot_iters = get_next_sub_slot_iters_and_difficulty(
        constants,
        len(block.finished_sub_slots) > 0, prev_b, blocks)[0]
    overflow = is_overflow_block(constants, signage_point_index)

    if (len(block.finished_sub_slots) > 0 and
            block.finished_sub_slots[0].challenge_chain.subepoch_summary_hash
            is not None):
        return None

    if can_finish_soon:
        deficit: uint8 = uint8(
            0)  # Assume that our deficit will go to zero soon
        can_finish_se = True
        if height_can_be_first_in_epoch(constants, uint32(prev_b.height + 2)):
            can_finish_epoch = True
            if (prev_b.height + 2) % constants.SUB_EPOCH_BLOCKS > 1:
                curr: BlockRecord = prev_b
                while curr.height % constants.SUB_EPOCH_BLOCKS > 0:
                    if (curr.sub_epoch_summary_included is not None
                            and curr.sub_epoch_summary_included.new_difficulty
                            is not None):
                        can_finish_epoch = False
                    curr = blocks.block_record(curr.prev_hash)

                if (curr.sub_epoch_summary_included is not None
                        and curr.sub_epoch_summary_included.new_difficulty
                        is not None):
                    can_finish_epoch = False
        elif height_can_be_first_in_epoch(
                constants,
                uint32(prev_b.height + constants.MAX_SUB_SLOT_BLOCKS + 2)):
            can_finish_epoch = True
        else:
            can_finish_epoch = False
    else:
        deficit = calculate_deficit(
            constants,
            uint32(prev_b.height + 1),
            prev_b,
            overflow,
            len(block.finished_sub_slots),
        )
        can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch(
            constants,
            blocks,
            uint32(prev_b.height + 1),
            prev_b.header_hash if prev_b is not None else None,
            deficit,
            False,
        )

    # 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,
            blocks,
            block.prev_header_hash,
            uint32(prev_b.height + 1),
            uint64(prev_b.weight -
                   blocks.block_record(prev_b.prev_hash).weight),
            deficit,
            False,  # Already checked above
            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,
            blocks,
            block.prev_header_hash,
            uint32(prev_b.height + 1),
            sub_slot_iters,
            deficit,
            False,  # Already checked above
            True,
            uint128(block.total_iters - ip_iters + sp_iters -
                    (sub_slot_iters if overflow else 0)),
            True,
        )

    return make_sub_epoch_summary(
        constants,
        blocks,
        uint32(prev_b.height + 2),
        prev_b,
        next_difficulty,
        next_sub_slot_iters,
    )
Exemplo n.º 21
0
def get_signage_point_vdf_info(
    constants: ConsensusConstants,
    finished_sub_slots: List[EndOfSubSlotBundle],
    overflow: bool,
    prev_b: Optional[BlockRecord],
    blocks: BlockchainInterface,
    sp_total_iters: uint128,
    sp_iters: uint64,
):
    """
    Returns the following information, for the VDF of the signage point at sp_total_iters.
    cc and rc challenge hash
    cc and rc input
    cc and rc iterations
    """

    new_sub_slot: bool = len(finished_sub_slots) > 0
    genesis_block: bool = prev_b is None

    if new_sub_slot and not overflow:
        # Case 1: start from start of this slot. Case of no overflow slots. Also includes genesis block after empty
        # slot(s), but not overflowing
        rc_vdf_challenge: bytes32 = finished_sub_slots[
            -1].reward_chain.get_hash()
        cc_vdf_challenge = finished_sub_slots[-1].challenge_chain.get_hash()
        sp_vdf_iters = sp_iters
        cc_vdf_input = ClassgroupElement.get_default_element()
    elif new_sub_slot and overflow and len(finished_sub_slots) > 1:
        # Case 2: start from start of prev slot. This is a rare case of empty prev slot. Includes genesis block after
        # 2 empty slots
        rc_vdf_challenge = finished_sub_slots[-2].reward_chain.get_hash()
        cc_vdf_challenge = finished_sub_slots[-2].challenge_chain.get_hash()
        sp_vdf_iters = sp_iters
        cc_vdf_input = ClassgroupElement.get_default_element()
    elif genesis_block:
        # Case 3: Genesis block case, first challenge
        rc_vdf_challenge = constants.GENESIS_CHALLENGE
        cc_vdf_challenge = constants.GENESIS_CHALLENGE
        sp_vdf_iters = sp_iters
        cc_vdf_input = ClassgroupElement.get_default_element()
    elif new_sub_slot and overflow and len(finished_sub_slots) == 1:
        # Case 4: Starting at prev will put us in the previous, sub-slot, since case 2 handled more empty slots
        assert prev_b is not None
        curr: BlockRecord = prev_b
        while not curr.first_in_sub_slot and curr.total_iters > sp_total_iters:
            curr = blocks.block_record(curr.prev_hash)
        if curr.total_iters < sp_total_iters:
            sp_vdf_iters = uint64(sp_total_iters - curr.total_iters)
            cc_vdf_input = curr.challenge_vdf_output
            rc_vdf_challenge = curr.reward_infusion_new_challenge
        else:
            assert curr.finished_reward_slot_hashes is not None
            sp_vdf_iters = sp_iters
            cc_vdf_input = ClassgroupElement.get_default_element()
            rc_vdf_challenge = curr.finished_reward_slot_hashes[-1]

        while not curr.first_in_sub_slot:
            curr = blocks.block_record(curr.prev_hash)
        assert curr.finished_challenge_slot_hashes is not None
        cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1]
    elif not new_sub_slot and overflow:
        # Case 5: prev is in the same sub slot and also overflow. Starting at prev does not skip any sub slots
        assert prev_b is not None
        curr = prev_b

        # Collects the last two finished slots
        if curr.first_in_sub_slot:
            assert curr.finished_challenge_slot_hashes is not None
            assert curr.finished_reward_slot_hashes is not None
            found_sub_slots = list(
                reversed(
                    list(
                        zip(
                            curr.finished_challenge_slot_hashes,
                            curr.finished_reward_slot_hashes,
                        ))))
        else:
            found_sub_slots = []
        sp_pre_sb: Optional[BlockRecord] = None
        while len(found_sub_slots) < 2 and curr.height > 0:
            if sp_pre_sb is None and curr.total_iters < sp_total_iters:
                sp_pre_sb = curr
            curr = blocks.block_record(curr.prev_hash)
            if curr.first_in_sub_slot:
                assert curr.finished_challenge_slot_hashes is not None
                assert curr.finished_reward_slot_hashes is not None
                found_sub_slots += list(
                    reversed(
                        list(
                            zip(
                                curr.finished_challenge_slot_hashes,
                                curr.finished_reward_slot_hashes,
                            ))))
        if sp_pre_sb is None and curr.total_iters < sp_total_iters:
            sp_pre_sb = curr
        if sp_pre_sb is not None:
            sp_vdf_iters = uint64(sp_total_iters - sp_pre_sb.total_iters)
            cc_vdf_input = sp_pre_sb.challenge_vdf_output
            rc_vdf_challenge = sp_pre_sb.reward_infusion_new_challenge
        else:
            sp_vdf_iters = sp_iters
            cc_vdf_input = ClassgroupElement.get_default_element()
            rc_vdf_challenge = found_sub_slots[1][1]
        cc_vdf_challenge = found_sub_slots[1][0]

    elif not new_sub_slot and not overflow:
        # Case 6: prev is in the same sub slot. Starting at prev does not skip any sub slots. We do not need
        # to go back another sub slot, because it's not overflow, so the VDF to signage point is this sub-slot.
        assert prev_b is not None
        curr = prev_b
        while not curr.first_in_sub_slot and curr.total_iters > sp_total_iters:
            curr = blocks.block_record(curr.prev_hash)
        if curr.total_iters < sp_total_iters:
            sp_vdf_iters = uint64(sp_total_iters - curr.total_iters)
            cc_vdf_input = curr.challenge_vdf_output
            rc_vdf_challenge = curr.reward_infusion_new_challenge
        else:
            assert curr.finished_reward_slot_hashes is not None
            sp_vdf_iters = sp_iters
            cc_vdf_input = ClassgroupElement.get_default_element()
            rc_vdf_challenge = curr.finished_reward_slot_hashes[-1]

        while not curr.first_in_sub_slot:
            curr = blocks.block_record(curr.prev_hash)
        assert curr.finished_challenge_slot_hashes is not None
        cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1]
    else:
        # All cases are handled above
        assert False

    return (
        cc_vdf_challenge,
        rc_vdf_challenge,
        cc_vdf_input,
        ClassgroupElement.get_default_element(),
        sp_vdf_iters,
        sp_vdf_iters,
    )
Exemplo n.º 22
0
def block_to_block_record(
    constants: ConsensusConstants,
    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
    prev_b = blocks.try_block_record(block.prev_header_hash)
    if block.height > 0:
        assert prev_b is not None
    sub_slot_iters, _ = get_next_sub_slot_iters_and_difficulty(
        constants, len(block.finished_sub_slots) > 0, prev_b, blocks
    )
    overflow = is_overflow_block(constants, block.reward_chain_block.signage_point_index)
    deficit = calculate_deficit(
        constants,
        block.height,
        prev_b,
        overflow,
        len(block.finished_sub_slots),
    )

    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_b is not None
        assert len(block.finished_sub_slots) > 0
        ses = make_sub_epoch_summary(
            constants,
            blocks,
            block.height,
            blocks.block_record(prev_b.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

    prev_transaction_block_height = uint32(0)
    curr: Optional[BlockRecord] = blocks.try_block_record(block.prev_header_hash)
    while curr is not None and not curr.is_transaction_block:
        curr = blocks.try_block_record(curr.prev_hash)

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

    return header_block_to_sub_block_record(
        constants,
        required_iters,
        block,
        sub_slot_iters,
        overflow,
        deficit,
        prev_transaction_block_height,
        ses,
    )
Exemplo n.º 23
0
async def pre_validate_blocks_multiprocessing(
    constants: ConsensusConstants,
    constants_json: Dict,
    block_records: BlockchainInterface,
    blocks: Sequence[Union[FullBlock, HeaderBlock]],
    pool: ProcessPoolExecutor,
    check_filter: bool,
    npc_results: Dict[uint32, NPCResult],
    get_block_generator: Optional[Callable],
    batch_size: int,
    wp_summaries: Optional[List[SubEpochSummary]] = None,
) -> Optional[List[PreValidationResult]]:
    """
    This method must be called under the blockchain lock
    If all the full blocks pass pre-validation, (only validates header), returns the list of required iters.
    if any validation issue occurs, returns False.

    Args:
        check_filter:
        constants_json:
        pool:
        constants:
        block_records:
        blocks: list of full blocks to validate (must be connected to current chain)
        npc_results
        get_block_generator
    """
    prev_b: Optional[BlockRecord] = None
    # Collects all the recent blocks (up to the previous sub-epoch)
    recent_blocks: Dict[bytes32, BlockRecord] = {}
    recent_blocks_compressed: Dict[bytes32, BlockRecord] = {}
    num_sub_slots_found = 0
    num_blocks_seen = 0
    if blocks[0].height > 0:
        if not block_records.contains_block(blocks[0].prev_header_hash):
            return [
                PreValidationResult(uint16(Err.INVALID_PREV_BLOCK_HASH.value),
                                    None, None)
            ]
        curr = block_records.block_record(blocks[0].prev_header_hash)
        num_sub_slots_to_look_for = 3 if curr.overflow else 2
        while (curr.sub_epoch_summary_included is None
               or num_blocks_seen < constants.NUMBER_OF_TIMESTAMPS
               or num_sub_slots_found < num_sub_slots_to_look_for
               ) and curr.height > 0:
            if num_blocks_seen < constants.NUMBER_OF_TIMESTAMPS or num_sub_slots_found < num_sub_slots_to_look_for:
                recent_blocks_compressed[curr.header_hash] = curr

            if curr.first_in_sub_slot:
                assert curr.finished_challenge_slot_hashes is not None
                num_sub_slots_found += len(curr.finished_challenge_slot_hashes)
            recent_blocks[curr.header_hash] = curr
            if curr.is_transaction_block:
                num_blocks_seen += 1
            curr = block_records.block_record(curr.prev_hash)
        recent_blocks[curr.header_hash] = curr
        recent_blocks_compressed[curr.header_hash] = curr
    block_record_was_present = []
    for block in blocks:
        block_record_was_present.append(
            block_records.contains_block(block.header_hash))

    diff_ssis: List[Tuple[uint64, uint64]] = []
    for block in blocks:
        if block.height != 0:
            assert block_records.contains_block(block.prev_header_hash)
            if prev_b is None:
                prev_b = block_records.block_record(block.prev_header_hash)

        sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty(
            constants,
            len(block.finished_sub_slots) > 0, prev_b, block_records)

        overflow = is_overflow_block(
            constants, block.reward_chain_block.signage_point_index)
        challenge = get_block_challenge(constants, block,
                                        BlockCache(recent_blocks),
                                        prev_b is None, overflow, False)
        if block.reward_chain_block.challenge_chain_sp_vdf is None:
            cc_sp_hash: bytes32 = challenge
        else:
            cc_sp_hash = block.reward_chain_block.challenge_chain_sp_vdf.output.get_hash(
            )
        q_str: Optional[
            bytes32] = block.reward_chain_block.proof_of_space.verify_and_get_quality_string(
                constants, challenge, cc_sp_hash)
        if q_str is None:
            for i, block_i in enumerate(blocks):
                if not block_record_was_present[
                        i] and block_records.contains_block(
                            block_i.header_hash):
                    block_records.remove_block_record(block_i.header_hash)
            return None

        required_iters: uint64 = calculate_iterations_quality(
            constants.DIFFICULTY_CONSTANT_FACTOR,
            q_str,
            block.reward_chain_block.proof_of_space.size,
            difficulty,
            cc_sp_hash,
        )

        block_rec = block_to_block_record(
            constants,
            block_records,
            required_iters,
            block,
            None,
        )

        if block_rec.sub_epoch_summary_included is not None and wp_summaries is not None:
            idx = int(block.height / constants.SUB_EPOCH_BLOCKS) - 1
            next_ses = wp_summaries[idx]
            if not block_rec.sub_epoch_summary_included.get_hash(
            ) == next_ses.get_hash():
                log.error(
                    "sub_epoch_summary does not match wp sub_epoch_summary list"
                )
                return None
        # Makes sure to not override the valid blocks already in block_records
        if not block_records.contains_block(block_rec.header_hash):
            block_records.add_block_record(
                block_rec)  # Temporarily add block to dict
            recent_blocks[block_rec.header_hash] = block_rec
            recent_blocks_compressed[block_rec.header_hash] = block_rec
        else:
            recent_blocks[block_rec.header_hash] = block_records.block_record(
                block_rec.header_hash)
            recent_blocks_compressed[
                block_rec.header_hash] = block_records.block_record(
                    block_rec.header_hash)
        prev_b = block_rec
        diff_ssis.append((difficulty, sub_slot_iters))

    block_dict: Dict[bytes32, Union[FullBlock, HeaderBlock]] = {}
    for i, block in enumerate(blocks):
        block_dict[block.header_hash] = block
        if not block_record_was_present[i]:
            block_records.remove_block_record(block.header_hash)

    recent_sb_compressed_pickled = {
        bytes(k): bytes(v)
        for k, v in recent_blocks_compressed.items()
    }
    npc_results_pickled = {}
    for k, v in npc_results.items():
        npc_results_pickled[k] = bytes(v)
    futures = []
    # Pool of workers to validate blocks concurrently
    for i in range(0, len(blocks), batch_size):
        end_i = min(i + batch_size, len(blocks))
        blocks_to_validate = blocks[i:end_i]
        if any([
                len(block.finished_sub_slots) > 0
                for block in blocks_to_validate
        ]):
            final_pickled = {
                bytes(k): bytes(v)
                for k, v in recent_blocks.items()
            }
        else:
            final_pickled = recent_sb_compressed_pickled
        b_pickled: Optional[List[bytes]] = None
        hb_pickled: Optional[List[bytes]] = None
        previous_generators: List[Optional[bytes]] = []
        for block in blocks_to_validate:
            # We ONLY add blocks which are in the past, based on header hashes (which are validated later) to the
            # prev blocks dict. This is important since these blocks are assumed to be valid and are used as previous
            # generator references
            prev_blocks_dict: Dict[uint32, Union[FullBlock, HeaderBlock]] = {}
            curr_b: Union[FullBlock, HeaderBlock] = block

            while curr_b.prev_header_hash in block_dict:
                curr_b = block_dict[curr_b.prev_header_hash]
                prev_blocks_dict[curr_b.header_hash] = curr_b

            if isinstance(block, FullBlock):
                assert get_block_generator is not None
                if b_pickled is None:
                    b_pickled = []
                b_pickled.append(bytes(block))
                try:
                    block_generator: Optional[
                        BlockGenerator] = await get_block_generator(
                            block, prev_blocks_dict)
                except ValueError:
                    return None
                if block_generator is not None:
                    previous_generators.append(bytes(block_generator))
                else:
                    previous_generators.append(None)
            else:
                if hb_pickled is None:
                    hb_pickled = []
                hb_pickled.append(bytes(block))

        futures.append(asyncio.get_running_loop().run_in_executor(
            pool,
            batch_pre_validate_blocks,
            constants_json,
            final_pickled,
            b_pickled,
            hb_pickled,
            previous_generators,
            npc_results_pickled,
            check_filter,
            [diff_ssis[j][0] for j in range(i, end_i)],
            [diff_ssis[j][1] for j in range(i, end_i)],
        ))
    # Collect all results into one flat list
    return [
        PreValidationResult.from_bytes(result)
        for batch_result in (await asyncio.gather(*futures))
        for result in batch_result
    ]
def _get_next_difficulty(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    prev_header_hash: bytes32,
    height: uint32,
    current_difficulty: uint64,
    deficit: uint8,
    block_at_height_included_ses: bool,
    new_slot: bool,
    signage_point_total_iters: uint128,
    skip_epoch_check=False,
) -> uint64:
    """
    Returns the difficulty of the next block that extends onto block.
    Used to calculate the number of iterations. WARNING: assumes that the block at height is not the first block
    in a sub-epoch.

    Args:
        constants: consensus constants being used for this chain
        blocks: dictionary from header hash to SBR of all included SBR
        prev_header_hash: header hash of the previous block
        height: the block height of the block to look at
        deficit: deficit of block at height height
        current_difficulty: difficulty at the infusion point of the block at height
        new_slot: whether or not there is a new slot after height
        signage_point_total_iters: signage point iters of the block at height
        skip_epoch_check: don't check correct epoch
    """
    next_height: uint32 = uint32(height + 1)

    if next_height < (constants.EPOCH_BLOCKS -
                      3 * constants.MAX_SUB_SLOT_BLOCKS):
        # We are in the first epoch
        return uint64(constants.DIFFICULTY_STARTING)

    if not blocks.contains_block(prev_header_hash):
        raise ValueError(f"Header hash {prev_header_hash} not in blocks")

    prev_b: BlockRecord = blocks.block_record(prev_header_hash)

    # If we are in the same slot as previous block, return same difficulty
    if not skip_epoch_check:
        _, can_finish_epoch = can_finish_sub_and_full_epoch(
            constants, blocks, height, prev_header_hash, deficit,
            block_at_height_included_ses)
        if not new_slot or not can_finish_epoch:
            return current_difficulty

    last_block_prev: BlockRecord = _get_second_to_last_transaction_block_in_previous_epoch(
        constants, blocks, prev_b)

    # This gets the last transaction block before this block's signage point. Assuming the block at height height
    # is the last block infused in the epoch: If this block ends up being a
    # transaction block, then last_block_curr will be the second to last tx block in the epoch. If this block
    # is not a transaction block, that means there was exactly one other tx block included in between our signage
    # point and infusion point, and therefore last_block_curr is the second to last as well.
    last_block_curr = prev_b
    while last_block_curr.total_iters > signage_point_total_iters or not last_block_curr.is_transaction_block:
        last_block_curr = blocks.block_record(last_block_curr.prev_hash)

    assert last_block_curr.timestamp is not None
    assert last_block_prev.timestamp is not None
    actual_epoch_time: uint64 = uint64(last_block_curr.timestamp -
                                       last_block_prev.timestamp)

    old_difficulty = uint64(prev_b.weight -
                            blocks.block_record(prev_b.prev_hash).weight)

    # Terms are rearranged so there is only one division.
    new_difficulty_precise = uint64(
        (last_block_curr.weight - last_block_prev.weight) *
        constants.SUB_SLOT_TIME_TARGET //
        (constants.SLOT_BLOCKS_TARGET * actual_epoch_time))

    # Only change by a max factor, to prevent attacks, as in greenpaper, and must be at least 1
    max_diff = uint64(constants.DIFFICULTY_CHANGE_MAX_FACTOR * old_difficulty)
    min_diff = uint64(old_difficulty // constants.DIFFICULTY_CHANGE_MAX_FACTOR)

    if new_difficulty_precise >= old_difficulty:
        new_difficulty_precise = uint64(min(new_difficulty_precise, max_diff))
    else:
        new_difficulty_precise = uint64(
            max([uint64(1), new_difficulty_precise, min_diff]))
    new_difficulty = truncate_to_significant_bits(new_difficulty_precise,
                                                  constants.SIGNIFICANT_BITS)
    assert count_significant_bits(new_difficulty) <= constants.SIGNIFICANT_BITS
    return uint64(new_difficulty)