Esempio n. 1
0
    def _can_infuse_unfinished_block(self, block: timelord_protocol.NewUnfinishedBlockTimelord) -> Optional[uint64]:
        assert self.last_state is not None
        sub_slot_iters = self.last_state.get_sub_slot_iters()
        difficulty = self.last_state.get_difficulty()
        ip_iters = self.last_state.get_last_ip()
        rc_block = block.reward_chain_block
        try:
            block_sp_iters, block_ip_iters = iters_from_block(
                self.constants,
                rc_block,
                sub_slot_iters,
                difficulty,
            )
        except Exception as e:
            log.warning(f"Received invalid unfinished block: {e}.")
            return None
        block_sp_total_iters = self.last_state.total_iters - ip_iters + block_sp_iters
        if is_overflow_block(self.constants, block.reward_chain_block.signage_point_index):
            block_sp_total_iters -= self.last_state.get_sub_slot_iters()
        found_index = -1
        for index, (rc, total_iters) in enumerate(self.last_state.reward_challenge_cache):
            if rc == block.rc_prev:
                found_index = index
                break
        if found_index == -1:
            log.warning(f"Will not infuse {block.rc_prev} because its reward chain challenge is not in the chain")
            return None

        new_block_iters = uint64(block_ip_iters - ip_iters)
        if len(self.last_state.reward_challenge_cache) > found_index + 1:
            if self.last_state.reward_challenge_cache[found_index + 1][1] < block_sp_total_iters:
                log.warning(
                    f"Will not infuse unfinished block {block.rc_prev} sp total iters {block_sp_total_iters}, "
                    f"because there is another infusion before its SP"
                )
                return None
            if self.last_state.reward_challenge_cache[found_index][1] > block_sp_total_iters:
                if not is_overflow_block(self.constants, block.reward_chain_block.signage_point_index):
                    log.error(
                        f"Will not infuse unfinished block {block.rc_prev}, sp total iters: {block_sp_total_iters}, "
                        f"because its iters are too low"
                    )
                return None

        if new_block_iters > 0:
            return new_block_iters
        return None
Esempio n. 2
0
    async def _check_for_new_ip(self, iter_to_look_for: uint64):
        if len(self.unfinished_blocks) == 0:
            return None
        infusion_iters = [
            iteration for iteration, t in self.iteration_to_proof_type.items() if t == IterationType.INFUSION_POINT
        ]
        for iteration in infusion_iters:
            if iteration != iter_to_look_for:
                continue
            proofs_with_iter = [
                (chain, info, proof)
                for chain, info, proof, label in self.proofs_finished
                if info.number_of_iterations == iteration and label == self.num_resets
            ]
            if self.last_state.get_challenge(Chain.INFUSED_CHALLENGE_CHAIN) is not None:
                chain_count = 3
            else:
                chain_count = 2
            if len(proofs_with_iter) == chain_count:
                block = None
                ip_iters = None
                for unfinished_block in self.unfinished_blocks:
                    try:
                        _, ip_iters = iters_from_block(
                            self.constants,
                            unfinished_block.reward_chain_block,
                            self.last_state.get_sub_slot_iters(),
                            self.last_state.get_difficulty(),
                        )
                    except Exception as e:
                        log.error(f"Error {e}")
                        continue
                    if ip_iters - self.last_state.get_last_ip() == iteration:
                        block = unfinished_block
                        break
                assert ip_iters is not None
                if block is not None:
                    ip_total_iters = self.last_state.get_total_iters() + iteration
                    challenge = block.reward_chain_block.get_hash()
                    icc_info: Optional[VDFInfo] = None
                    icc_proof: Optional[VDFProof] = None
                    cc_info: Optional[VDFInfo] = None
                    cc_proof: Optional[VDFProof] = None
                    rc_info: Optional[VDFInfo] = None
                    rc_proof: Optional[VDFProof] = None
                    for chain, info, proof in proofs_with_iter:
                        if chain == Chain.CHALLENGE_CHAIN:
                            cc_info = info
                            cc_proof = proof
                        if chain == Chain.REWARD_CHAIN:
                            rc_info = info
                            rc_proof = proof
                        if chain == Chain.INFUSED_CHALLENGE_CHAIN:
                            icc_info = info
                            icc_proof = proof
                    if cc_info is None or cc_proof is None or rc_info is None or rc_proof is None:
                        log.error(f"Insufficient VDF proofs for infusion point ch: {challenge} iterations:{iteration}")
                        return None

                    rc_challenge = self.last_state.get_challenge(Chain.REWARD_CHAIN)
                    if rc_info.challenge != rc_challenge:
                        assert rc_challenge is not None
                        log.warning(
                            f"Do not have correct challenge {rc_challenge.hex()} "
                            f"has {rc_info.challenge}, partial hash {block.reward_chain_block.get_hash()}"
                        )
                        # This proof is on an outdated challenge, so don't use it
                        continue

                    self.iters_finished.add(iter_to_look_for)
                    self.last_active_time = time.time()
                    log.debug(f"Generated infusion point for challenge: {challenge} iterations: {iteration}.")

                    overflow = is_overflow_block(self.constants, block.reward_chain_block.signage_point_index)

                    if not self.last_state.can_infuse_block(overflow):
                        log.warning("Too many blocks, or overflow in new epoch, cannot infuse, discarding")
                        return None

                    cc_info = dataclasses.replace(cc_info, number_of_iterations=ip_iters)
                    response = timelord_protocol.NewInfusionPointVDF(
                        challenge,
                        cc_info,
                        cc_proof,
                        rc_info,
                        rc_proof,
                        icc_info,
                        icc_proof,
                    )
                    msg = make_msg(ProtocolMessageTypes.new_infusion_point_vdf, response)
                    if self.server is not None:
                        await self.server.send_to_all([msg], NodeType.FULL_NODE)

                    self.proofs_finished = self._clear_proof_list(iteration)

                    if (
                        self.last_state.get_last_block_total_iters() is None
                        and not self.last_state.state_type == StateType.FIRST_SUB_SLOT
                    ):
                        # We don't know when the last block was, so we can't make peaks
                        return None

                    sp_total_iters = (
                        ip_total_iters
                        - ip_iters
                        + calculate_sp_iters(
                            self.constants,
                            block.sub_slot_iters,
                            block.reward_chain_block.signage_point_index,
                        )
                        - (block.sub_slot_iters if overflow else 0)
                    )
                    if self.last_state.state_type == StateType.FIRST_SUB_SLOT:
                        is_transaction_block = True
                        height: uint32 = uint32(0)
                    else:
                        last_block_ti = self.last_state.get_last_block_total_iters()
                        assert last_block_ti is not None
                        is_transaction_block = last_block_ti < sp_total_iters
                        height = uint32(self.last_state.get_height() + 1)

                    if height < 5:
                        # Don't directly update our state for the first few blocks, because we cannot validate
                        # whether the pre-farm is correct
                        return None

                    new_reward_chain_block = RewardChainBlock(
                        uint128(self.last_state.get_weight() + block.difficulty),
                        height,
                        uint128(ip_total_iters),
                        block.reward_chain_block.signage_point_index,
                        block.reward_chain_block.pos_ss_cc_challenge_hash,
                        block.reward_chain_block.proof_of_space,
                        block.reward_chain_block.challenge_chain_sp_vdf,
                        block.reward_chain_block.challenge_chain_sp_signature,
                        cc_info,
                        block.reward_chain_block.reward_chain_sp_vdf,
                        block.reward_chain_block.reward_chain_sp_signature,
                        rc_info,
                        icc_info,
                        is_transaction_block,
                    )
                    if self.last_state.state_type == StateType.FIRST_SUB_SLOT:
                        # Genesis
                        new_deficit = self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK - 1
                    elif overflow and self.last_state.deficit == self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK:
                        if self.last_state.peak is not None:
                            assert self.last_state.subslot_end is None
                            # This means the previous block is also an overflow block, and did not manage
                            # to lower the deficit, therefore we cannot lower it either. (new slot)
                            new_deficit = self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK
                        else:
                            # This means we are the first infusion in this sub-slot. This may be a new slot or not.
                            assert self.last_state.subslot_end is not None
                            if self.last_state.subslot_end.infused_challenge_chain is None:
                                # There is no ICC, which means we are not finishing a slot. We can reduce the deficit.
                                new_deficit = self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK - 1
                            else:
                                # There is an ICC, which means we are finishing a slot. Different slot, so can't change
                                # the deficit
                                new_deficit = self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK
                    else:
                        new_deficit = max(self.last_state.deficit - 1, 0)

                    if new_deficit == self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK - 1:
                        last_csb_or_eos = ip_total_iters
                    else:
                        last_csb_or_eos = self.last_state.last_challenge_sb_or_eos_total_iters

                    if self.last_state.just_infused_sub_epoch_summary():
                        new_sub_epoch_summary = None
                        passed_ses_height_but_not_yet_included = False
                    else:
                        new_sub_epoch_summary = block.sub_epoch_summary
                        if new_reward_chain_block.height % self.constants.SUB_EPOCH_BLOCKS == 0:
                            passed_ses_height_but_not_yet_included = True
                        else:
                            passed_ses_height_but_not_yet_included = (
                                self.last_state.get_passed_ses_height_but_not_yet_included()
                            )

                    self.new_peak = timelord_protocol.NewPeakTimelord(
                        new_reward_chain_block,
                        block.difficulty,
                        uint8(new_deficit),
                        block.sub_slot_iters,
                        new_sub_epoch_summary,
                        self.last_state.reward_challenge_cache,
                        uint128(last_csb_or_eos),
                        passed_ses_height_but_not_yet_included,
                    )

                    await self._handle_new_peak()
                    # Break so we alternate between checking SP and IP
                    break
Esempio n. 3
0
    def set_state(self, state: Union[timelord_protocol.NewPeakTimelord,
                                     EndOfSubSlotBundle]):
        if isinstance(state, timelord_protocol.NewPeakTimelord):
            self.state_type = StateType.PEAK
            self.peak = state
            self.subslot_end = None
            _, self.last_ip = iters_from_block(
                self.constants,
                state.reward_chain_block,
                state.sub_slot_iters,
                state.difficulty,
            )
            self.deficit = state.deficit
            self.sub_epoch_summary = state.sub_epoch_summary
            self.last_weight = state.reward_chain_block.weight
            self.last_height = state.reward_chain_block.height
            self.total_iters = state.reward_chain_block.total_iters
            self.last_peak_challenge = state.reward_chain_block.get_hash()
            self.difficulty = state.difficulty
            self.sub_slot_iters = state.sub_slot_iters
            if state.reward_chain_block.is_transaction_block:
                self.last_block_total_iters = self.total_iters
            self.reward_challenge_cache = state.previous_reward_challenges
            self.last_challenge_sb_or_eos_total_iters = self.peak.last_challenge_sb_or_eos_total_iters
            self.new_epoch = False
            if (self.peak.reward_chain_block.height +
                    1) % self.constants.SUB_EPOCH_BLOCKS == 0:
                self.passed_ses_height_but_not_yet_included = True
            else:
                self.passed_ses_height_but_not_yet_included = state.passes_ses_height_but_not_yet_included
            log.warning(
                f"Signage point index: {self.peak.reward_chain_block.signage_point_index}"
            )
        elif isinstance(state, EndOfSubSlotBundle):
            self.state_type = StateType.END_OF_SUB_SLOT
            if self.peak is not None:
                self.total_iters = uint128(self.total_iters -
                                           self.get_last_ip() +
                                           self.sub_slot_iters)
            else:
                self.total_iters = uint128(self.total_iters +
                                           self.sub_slot_iters)
            self.peak = None
            self.subslot_end = state
            self.last_ip = uint64(0)
            self.deficit = state.reward_chain.deficit
            if state.challenge_chain.new_difficulty is not None:
                assert state.challenge_chain.new_sub_slot_iters is not None
                self.difficulty = state.challenge_chain.new_difficulty
                self.sub_slot_iters = state.challenge_chain.new_sub_slot_iters
                self.new_epoch = True
            else:
                self.new_epoch = False
            if state.challenge_chain.subepoch_summary_hash is not None:
                self.infused_ses = True
                self.passed_ses_height_but_not_yet_included = False
            else:
                self.infused_ses = False
                self.passed_ses_height_but_not_yet_included = self.passed_ses_height_but_not_yet_included
            self.last_challenge_sb_or_eos_total_iters = self.total_iters
        else:
            self.passed_ses_height_but_not_yet_included = self.passed_ses_height_but_not_yet_included
            self.new_epoch = False

        self.reward_challenge_cache.append(
            (self.get_challenge(Chain.REWARD_CHAIN), self.total_iters))
        log.info(
            f"Updated timelord peak to {self.get_challenge(Chain.REWARD_CHAIN)}, total iters: {self.total_iters}"
        )
        while len(self.reward_challenge_cache
                  ) > 2 * self.constants.MAX_SUB_SLOT_BLOCKS:
            self.reward_challenge_cache.pop(0)
    def set_state(self, state: Union[timelord_protocol.NewPeakTimelord,
                                     EndOfSubSlotBundle]):
        if isinstance(state, timelord_protocol.NewPeakTimelord):
            self.state_type = StateType.PEAK
            self.peak = state
            self.subslot_end = None
            _, self.last_ip = iters_from_block(
                self.constants,
                state.reward_chain_block,
                state.sub_slot_iters,
                state.difficulty,
            )
            self.deficit = state.deficit
            self.sub_epoch_summary = state.sub_epoch_summary
            self.last_weight = state.reward_chain_block.weight
            self.last_height = state.reward_chain_block.height
            self.total_iters = state.reward_chain_block.total_iters
            self.last_peak_challenge = state.reward_chain_block.get_hash()
            self.difficulty = state.difficulty
            self.sub_slot_iters = state.sub_slot_iters
            if state.reward_chain_block.is_transaction_block:
                self.last_block_total_iters = self.total_iters
            self.reward_challenge_cache = state.previous_reward_challenges
            self.last_challenge_sb_or_eos_total_iters = self.peak.last_challenge_sb_or_eos_total_iters
            self.new_epoch = False
            if (self.peak.reward_chain_block.height +
                    1) % self.constants.SUB_EPOCH_BLOCKS == 0:
                self.passed_ses_height_but_not_yet_included = True
            else:
                self.passed_ses_height_but_not_yet_included = state.passes_ses_height_but_not_yet_included
        elif isinstance(state, EndOfSubSlotBundle):
            self.state_type = StateType.END_OF_SUB_SLOT
            if self.peak is not None:
                self.total_iters = uint128(self.total_iters -
                                           self.get_last_ip() +
                                           self.sub_slot_iters)
            else:
                self.total_iters = uint128(self.total_iters +
                                           self.sub_slot_iters)
            self.peak = None
            self.subslot_end = state
            self.last_ip = uint64(0)
            self.deficit = state.reward_chain.deficit
            if state.challenge_chain.new_difficulty is not None:
                assert state.challenge_chain.new_sub_slot_iters is not None
                self.difficulty = state.challenge_chain.new_difficulty
                self.sub_slot_iters = state.challenge_chain.new_sub_slot_iters
                self.new_epoch = True
            else:
                self.new_epoch = False
            if state.challenge_chain.subepoch_summary_hash is not None:
                self.infused_ses = True
                self.passed_ses_height_but_not_yet_included = False
            else:
                self.infused_ses = False
                # Since we have a new sub slot which is not an end of subepoch,
                # we will use the last value that we saw for
                # passed_ses_height_but_not_yet_included
            self.last_challenge_sb_or_eos_total_iters = self.total_iters
        else:
            assert False

        # TODO: address hint error and remove ignore
        #       error: Argument 1 to "append" of "list" has incompatible type "Tuple[Optional[bytes32], uint128]";
        #       expected "Tuple[bytes32, uint128]"  [arg-type]
        self.reward_challenge_cache.append(
            (self.get_challenge(Chain.REWARD_CHAIN),
             self.total_iters))  # type: ignore[arg-type]  # noqa: E501
        log.info(
            f"Updated timelord peak to {self.get_challenge(Chain.REWARD_CHAIN)}, total iters: {self.total_iters}"
        )
        while len(self.reward_challenge_cache
                  ) > 2 * self.constants.MAX_SUB_SLOT_BLOCKS:
            self.reward_challenge_cache.pop(0)