def get_vdf_info_and_proof( constants: ConsensusConstants, vdf_input: ClassgroupElement, challenge_hash: bytes32, number_iters: uint64, ) -> Tuple[VDFInfo, VDFProof]: int_size = (constants.DISCRIMINANT_SIZE_BITS + 16) >> 4 result: bytes = prove( bytes(challenge_hash), str(vdf_input.a), str(vdf_input.b), constants.DISCRIMINANT_SIZE_BITS, number_iters, ) output = ClassgroupElement( int512(int.from_bytes( result[0:int_size], "big", signed=True, )), int512( int.from_bytes( result[int_size:2 * int_size], "big", signed=True, )), ) proof_bytes = result[2 * int_size:4 * int_size] return VDFInfo(challenge_hash, number_iters, output), VDFProof(uint8(0), proof_bytes)
def get_vdf_info_and_proof( constants: ConsensusConstants, vdf_input: ClassgroupElement, challenge_hash: bytes32, number_iters: uint64, ) -> Tuple[VDFInfo, VDFProof]: form_size = ClassgroupElement.get_size(constants) result: bytes = prove( bytes(challenge_hash), vdf_input.data, constants.DISCRIMINANT_SIZE_BITS, number_iters, ) output = ClassgroupElement.from_bytes(result[:form_size]) proof_bytes = result[form_size:2 * form_size] return VDFInfo(challenge_hash, number_iters, output), VDFProof(uint8(0), proof_bytes)
def validate_finished_header_block( constants: ConsensusConstants, sub_blocks: BlockchainInterface, header_block: HeaderBlock, check_filter: bool, expected_difficulty: uint64, expected_sub_slot_iters: uint64, ) -> Tuple[Optional[uint64], Optional[ValidationError]]: """ Fully validates the header of a sub-block. A header block is the same as a full block, but without transactions and transaction info. Returns (required_iters, error). """ unfinished_header_block = UnfinishedHeaderBlock( header_block.finished_sub_slots, header_block.reward_chain_sub_block.get_unfinished(), header_block.challenge_chain_sp_proof, header_block.reward_chain_sp_proof, header_block.foliage_sub_block, header_block.foliage_block, header_block.transactions_filter, ) required_iters, validate_unfinished_err = validate_unfinished_header_block( constants, sub_blocks, unfinished_header_block, check_filter, expected_difficulty, expected_sub_slot_iters, False, ) genesis_block = False if validate_unfinished_err is not None: return None, validate_unfinished_err assert required_iters is not None if header_block.height == 0: prev_sb: Optional[SubBlockRecord] = None genesis_block = True else: prev_sb = sub_blocks.sub_block_record(header_block.prev_header_hash) new_sub_slot: bool = len(header_block.finished_sub_slots) > 0 ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_sub_block.signage_point_index, required_iters, ) if not genesis_block: assert prev_sb is not None # 27. Check sub-block height if header_block.height != prev_sb.height + 1: return None, ValidationError(Err.INVALID_HEIGHT) # 28. Check weight if header_block.weight != prev_sb.weight + expected_difficulty: log.error( f"INVALID WEIGHT: {header_block} {prev_sb} {expected_difficulty}" ) return None, ValidationError(Err.INVALID_WEIGHT) else: if header_block.height != uint32(0): return None, ValidationError(Err.INVALID_HEIGHT) if header_block.weight != constants.DIFFICULTY_STARTING: return None, ValidationError(Err.INVALID_WEIGHT) # RC vdf challenge is taken from more recent of (slot start, prev_block) if genesis_block: cc_vdf_output = ClassgroupElement.get_default_element() ip_vdf_iters = ip_iters if new_sub_slot: rc_vdf_challenge = header_block.finished_sub_slots[ -1].reward_chain.get_hash() else: rc_vdf_challenge = constants.GENESIS_CHALLENGE else: assert prev_sb is not None if new_sub_slot: # slot start is more recent rc_vdf_challenge = header_block.finished_sub_slots[ -1].reward_chain.get_hash() ip_vdf_iters = ip_iters cc_vdf_output = ClassgroupElement.get_default_element() else: # Prev sb is more recent rc_vdf_challenge = prev_sb.reward_infusion_new_challenge ip_vdf_iters = uint64( header_block.reward_chain_sub_block.total_iters - prev_sb.total_iters) cc_vdf_output = prev_sb.challenge_vdf_output # 29. Check challenge chain infusion point VDF if new_sub_slot: cc_vdf_challenge = header_block.finished_sub_slots[ -1].challenge_chain.get_hash() else: # Not first sub-block in slot if genesis_block: # genesis block cc_vdf_challenge = constants.GENESIS_CHALLENGE else: assert prev_sb is not None # Not genesis block, go back to first sub-block in slot curr = prev_sb while curr.finished_challenge_slot_hashes is None: curr = sub_blocks.sub_block_record(curr.prev_hash) cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1] cc_target_vdf_info = VDFInfo( cc_vdf_challenge, ip_vdf_iters, header_block.reward_chain_sub_block.challenge_chain_ip_vdf.output, ) if header_block.reward_chain_sub_block.challenge_chain_ip_vdf != dataclasses.replace( cc_target_vdf_info, number_of_iterations=ip_iters, ): expected = dataclasses.replace( cc_target_vdf_info, number_of_iterations=ip_iters, ) log.error( f"{header_block.reward_chain_sub_block.challenge_chain_ip_vdf }. expected {expected}" ) log.error(f"Block: {header_block}") return None, ValidationError(Err.INVALID_CC_IP_VDF) if not header_block.challenge_chain_ip_proof.is_valid( constants, cc_vdf_output, cc_target_vdf_info, None, ): log.error(f"Did not validate, output {cc_vdf_output}") log.error(f"Block: {header_block}") return None, ValidationError(Err.INVALID_CC_IP_VDF) # 30. Check reward chain infusion point VDF rc_target_vdf_info = VDFInfo( rc_vdf_challenge, ip_vdf_iters, header_block.reward_chain_sub_block.reward_chain_ip_vdf.output, ) if not header_block.reward_chain_ip_proof.is_valid( constants, ClassgroupElement.get_default_element(), header_block.reward_chain_sub_block.reward_chain_ip_vdf, rc_target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_IP_VDF) # 31. Check infused challenge chain infusion point VDF if not genesis_block: overflow = is_overflow_sub_block( constants, header_block.reward_chain_sub_block.signage_point_index) deficit = calculate_deficit( constants, header_block.height, prev_sb, overflow, len(header_block.finished_sub_slots), ) if header_block.reward_chain_sub_block.infused_challenge_chain_ip_vdf is None: # If we don't have an ICC chain, deficit must be 4 or 5 if deficit < constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1: return None, ValidationError(Err.INVALID_ICC_VDF) else: assert header_block.infused_challenge_chain_ip_proof is not None # If we have an ICC chain, deficit must be 0, 1, 2 or 3 if deficit >= constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1: return ( None, ValidationError( Err.INVALID_ICC_VDF, f"icc vdf and deficit is bigger or equal to {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1}", ), ) if new_sub_slot: last_ss = header_block.finished_sub_slots[-1] assert last_ss.infused_challenge_chain is not None icc_vdf_challenge: bytes32 = last_ss.infused_challenge_chain.get_hash( ) icc_vdf_input = ClassgroupElement.get_default_element() else: assert prev_sb is not None if prev_sb.is_challenge_sub_block(constants): icc_vdf_input = ClassgroupElement.get_default_element() else: icc_vdf_input = prev_sb.infused_challenge_vdf_output curr = prev_sb while curr.finished_infused_challenge_slot_hashes is None and not curr.is_challenge_sub_block( constants): curr = sub_blocks.sub_block_record(curr.prev_hash) if curr.is_challenge_sub_block(constants): icc_vdf_challenge = curr.challenge_block_info_hash else: assert curr.finished_infused_challenge_slot_hashes is not None icc_vdf_challenge = curr.finished_infused_challenge_slot_hashes[ -1] icc_target_vdf_info = VDFInfo( icc_vdf_challenge, ip_vdf_iters, header_block.reward_chain_sub_block. infused_challenge_chain_ip_vdf.output, ) if not header_block.infused_challenge_chain_ip_proof.is_valid( constants, icc_vdf_input, header_block.reward_chain_sub_block. infused_challenge_chain_ip_vdf, icc_target_vdf_info, ): return None, ValidationError(Err.INVALID_ICC_VDF, "invalid icc proof") else: if header_block.infused_challenge_chain_ip_proof is not None: return None, ValidationError(Err.INVALID_ICC_VDF) # 32. Check reward block hash if header_block.foliage_sub_block.reward_block_hash != header_block.reward_chain_sub_block.get_hash( ): return None, ValidationError(Err.INVALID_REWARD_BLOCK_HASH) # 33. Check reward block is_block if (header_block.foliage_sub_block.foliage_block_hash is not None) != header_block.reward_chain_sub_block.is_block: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) return required_iters, None
def validate_unfinished_header_block( constants: ConsensusConstants, sub_blocks: BlockchainInterface, header_block: UnfinishedHeaderBlock, check_filter: bool, expected_difficulty: uint64, expected_sub_slot_iters: uint64, skip_overflow_last_ss_validation: bool = False, skip_vdf_is_valid: bool = False, ) -> Tuple[Optional[uint64], Optional[ValidationError]]: """ Validates an unfinished header block. This is a block without the infusion VDFs (unfinished) and without transactions and transaction info (header). Returns (required_iters, error). This method is meant to validate only the unfinished part of the sub-block. However, the finished_sub_slots refers to all sub-slots that were finishes from the previous sub-block's infusion point, up to this sub-blocks infusion point. Therefore, in the case where this is an overflow sub-block, and the last sub-slot is not yet released, header_block.finished_sub_slots will be missing one sub-slot. In this case, skip_overflow_last_ss_validation must be set to True. This will skip validation of end of slots, sub-epochs, and lead to other small tweaks in validation. """ # 1. Check that the previous block exists in the blockchain, or that it is correct prev_sb = sub_blocks.try_sub_block(header_block.prev_header_hash) genesis_block = prev_sb is None if genesis_block and header_block.prev_header_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) overflow = is_overflow_sub_block( constants, header_block.reward_chain_sub_block.signage_point_index) if skip_overflow_last_ss_validation and overflow: finished_sub_slots_since_prev = len( header_block.finished_sub_slots) + 1 else: finished_sub_slots_since_prev = len(header_block.finished_sub_slots) new_sub_slot: bool = finished_sub_slots_since_prev > 0 can_finish_se: bool = False can_finish_epoch: bool = False if genesis_block: height: uint32 = uint32(0) assert expected_difficulty == constants.DIFFICULTY_STARTING assert expected_sub_slot_iters == constants.SUB_SLOT_ITERS_STARTING else: assert prev_sb is not None height = uint32(prev_sb.height + 1) if prev_sb.sub_epoch_summary_included is not None: can_finish_se, can_finish_epoch = False, False else: if new_sub_slot: can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch( constants, prev_sb.height, prev_sb.deficit, sub_blocks, prev_sb.prev_hash, False, ) else: can_finish_se = False can_finish_epoch = False # 2. Check finished slots that have been crossed since prev_sb ses_hash: Optional[bytes32] = None if new_sub_slot and not skip_overflow_last_ss_validation: # Finished a slot(s) since previous block. The first sub-slot must have at least one sub-block, and all # subsequent sub-slots must be empty for finished_sub_slot_n, sub_slot in enumerate( header_block.finished_sub_slots): # Start of slot challenge is fetched from SP challenge_hash: bytes32 = sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.challenge if finished_sub_slot_n == 0: if genesis_block: # 2a. check sub-slot challenge hash for genesis block if challenge_hash != constants.GENESIS_CHALLENGE: return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) else: assert prev_sb is not None curr: SubBlockRecord = prev_sb while not curr.first_in_sub_slot: curr = sub_blocks.sub_block_record(curr.prev_hash) assert curr.finished_challenge_slot_hashes is not None # 2b. check sub-slot challenge hash for non-genesis block if not curr.finished_challenge_slot_hashes[ -1] == challenge_hash: print(curr.finished_challenge_slot_hashes[-1], challenge_hash) return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) else: # 2c. check sub-slot challenge hash for empty slot if (not header_block.finished_sub_slots[ finished_sub_slot_n - 1].challenge_chain.get_hash() == challenge_hash): return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) if genesis_block: # 2d. Validate that genesis block has no ICC if sub_slot.infused_challenge_chain is not None: return None, ValidationError(Err.SHOULD_NOT_HAVE_ICC) else: assert prev_sb is not None icc_iters_committed: Optional[uint64] = None icc_iters_proof: Optional[uint64] = None icc_challenge_hash: Optional[bytes32] = None icc_vdf_input = None if prev_sb.deficit < constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: # There should be no ICC chain if the last sub block's deficit is 16 # Prev sb's deficit is 0, 1, 2, 3, or 4 if finished_sub_slot_n == 0: # This is the first sub slot after the last sb, which must have deficit 1-4, and thus an ICC curr = prev_sb while not curr.is_challenge_sub_block( constants) and not curr.first_in_sub_slot: curr = sub_blocks.sub_block_record(curr.prev_hash) if curr.is_challenge_sub_block(constants): icc_challenge_hash = curr.challenge_block_info_hash icc_iters_committed = uint64( prev_sb.sub_slot_iters - curr.ip_iters(constants)) else: assert curr.finished_infused_challenge_slot_hashes is not None icc_challenge_hash = curr.finished_infused_challenge_slot_hashes[ -1] icc_iters_committed = prev_sb.sub_slot_iters icc_iters_proof = uint64(prev_sb.sub_slot_iters - prev_sb.ip_iters(constants)) if prev_sb.is_challenge_sub_block(constants): icc_vdf_input = ClassgroupElement.get_default_element( ) else: icc_vdf_input = prev_sb.infused_challenge_vdf_output else: # This is not the first sub slot after the last sub block, so we might not have an ICC if (header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.deficit < constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK): finished_ss = header_block.finished_sub_slots[ finished_sub_slot_n - 1] assert finished_ss.infused_challenge_chain is not None # Only sets the icc iff the previous sub slots deficit is 4 or less icc_challenge_hash = finished_ss.infused_challenge_chain.get_hash( ) icc_iters_committed = prev_sb.sub_slot_iters icc_iters_proof = icc_iters_committed icc_vdf_input = ClassgroupElement.get_default_element( ) # 2e. Validate that there is not icc iff icc_challenge hash is None assert (sub_slot.infused_challenge_chain is None) == (icc_challenge_hash is None) if sub_slot.infused_challenge_chain is not None: assert icc_vdf_input is not None assert icc_iters_proof is not None assert icc_challenge_hash is not None assert sub_slot.proofs.infused_challenge_chain_slot_proof is not None # 2f. Check infused challenge chain sub-slot VDF # Only validate from prev_sb to optimize target_vdf_info = VDFInfo( icc_challenge_hash, icc_iters_proof, sub_slot.infused_challenge_chain. infused_challenge_chain_end_of_slot_vdf.output, ) if sub_slot.infused_challenge_chain.infused_challenge_chain_end_of_slot_vdf != dataclasses.replace( target_vdf_info, number_of_iterations=icc_iters_committed, ): return None, ValidationError(Err.INVALID_ICC_EOS_VDF) if not skip_vdf_is_valid and not sub_slot.proofs.infused_challenge_chain_slot_proof.is_valid( constants, icc_vdf_input, target_vdf_info, None): return None, ValidationError(Err.INVALID_ICC_EOS_VDF) if sub_slot.reward_chain.deficit == constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: # 2g. Check infused challenge sub-slot hash in challenge chain, deficit 16 if (sub_slot.infused_challenge_chain.get_hash() != sub_slot.challenge_chain. infused_challenge_chain_sub_slot_hash): return None, ValidationError( Err.INVALID_ICC_HASH_CC) else: # 2h. Check infused challenge sub-slot hash not included for other deficits if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError( Err.INVALID_ICC_HASH_CC) # 2i. Check infused challenge sub-slot hash in reward sub-slot if (sub_slot.infused_challenge_chain.get_hash() != sub_slot.reward_chain. infused_challenge_chain_sub_slot_hash): return None, ValidationError(Err.INVALID_ICC_HASH_RC) else: # 2j. If no icc, check that the cc doesn't include it if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError(Err.INVALID_ICC_HASH_CC) # 2k. If no icc, check that the cc doesn't include it if sub_slot.reward_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError(Err.INVALID_ICC_HASH_RC) if sub_slot.challenge_chain.subepoch_summary_hash is not None: assert ses_hash is None # Only one of the slots can have it ses_hash = sub_slot.challenge_chain.subepoch_summary_hash # 2l. check sub-epoch summary hash is None for empty slots if finished_sub_slot_n != 0: if sub_slot.challenge_chain.subepoch_summary_hash is not None: return None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH) if can_finish_epoch and sub_slot.challenge_chain.subepoch_summary_hash is not None: # 2m. Check new difficulty and ssi if sub_slot.challenge_chain.new_sub_slot_iters != expected_sub_slot_iters: return None, ValidationError( Err.INVALID_NEW_SUB_SLOT_ITERS) if sub_slot.challenge_chain.new_difficulty != expected_difficulty: return None, ValidationError(Err.INVALID_NEW_DIFFICULTY) else: # 2n. Check new difficulty and ssi are None if we don't finish epoch if sub_slot.challenge_chain.new_sub_slot_iters is not None: return None, ValidationError( Err.INVALID_NEW_SUB_SLOT_ITERS) if sub_slot.challenge_chain.new_difficulty is not None: return None, ValidationError(Err.INVALID_NEW_DIFFICULTY) # 2o. Check challenge sub-slot hash in reward sub-slot if sub_slot.challenge_chain.get_hash( ) != sub_slot.reward_chain.challenge_chain_sub_slot_hash: return ( None, ValidationError( Err.INVALID_CHALLENGE_SLOT_HASH_RC, "sub-slot hash in reward sub-slot mismatch", ), ) eos_vdf_iters: uint64 = expected_sub_slot_iters cc_start_element: ClassgroupElement = ClassgroupElement.get_default_element( ) cc_eos_vdf_challenge: bytes32 = challenge_hash if genesis_block: if finished_sub_slot_n == 0: # First block, one empty slot. prior_point is the initial challenge rc_eos_vdf_challenge: bytes32 = constants.GENESIS_CHALLENGE cc_eos_vdf_challenge = constants.GENESIS_CHALLENGE else: # First block, but have at least two empty slots rc_eos_vdf_challenge = header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.get_hash() else: assert prev_sb is not None if finished_sub_slot_n == 0: # No empty slots, so the starting point of VDF is the last reward block. Uses # the same IPS as the previous block, since it's the same slot rc_eos_vdf_challenge = prev_sb.reward_infusion_new_challenge eos_vdf_iters = uint64(prev_sb.sub_slot_iters - prev_sb.ip_iters(constants)) cc_start_element = prev_sb.challenge_vdf_output else: # At least one empty slot, so use previous slot hash. IPS might change because it's a new slot rc_eos_vdf_challenge = header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.get_hash() # 2p. Check end of reward slot VDF target_vdf_info = VDFInfo( rc_eos_vdf_challenge, eos_vdf_iters, sub_slot.reward_chain.end_of_slot_vdf.output, ) if not skip_vdf_is_valid and not sub_slot.proofs.reward_chain_slot_proof.is_valid( constants, ClassgroupElement.get_default_element(), sub_slot.reward_chain.end_of_slot_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_EOS_VDF) # 2q. Check challenge chain sub-slot VDF partial_cc_vdf_info = VDFInfo( cc_eos_vdf_challenge, eos_vdf_iters, sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf. output, ) if genesis_block: cc_eos_vdf_info_iters = constants.SUB_SLOT_ITERS_STARTING else: assert prev_sb is not None if finished_sub_slot_n == 0: cc_eos_vdf_info_iters = prev_sb.sub_slot_iters else: cc_eos_vdf_info_iters = expected_sub_slot_iters # Check that the modified data is correct if sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf != dataclasses.replace( partial_cc_vdf_info, number_of_iterations=cc_eos_vdf_info_iters, ): return None, ValidationError( Err.INVALID_CC_EOS_VDF, "wrong challenge chain end of slot vdf") # Pass in None for target info since we are only checking the proof from the temporary point, # but the challenge_chain_end_of_slot_vdf actually starts from the start of slot (for light clients) if not skip_vdf_is_valid and not sub_slot.proofs.challenge_chain_slot_proof.is_valid( constants, cc_start_element, partial_cc_vdf_info, None): return None, ValidationError(Err.INVALID_CC_EOS_VDF) if genesis_block: # 2r. Check deficit (MIN_SUB.. deficit edge case for genesis block) if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: return ( None, ValidationError( Err.INVALID_DEFICIT, f"genesis, expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}", ), ) else: assert prev_sb is not None if prev_sb.deficit == 0: # 2s. If prev sb had deficit 0, resets deficit to MIN_SUB_BLOCK_PER_CHALLENGE_BLOCK if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: log.error( constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK, ) return ( None, ValidationError( Err.INVALID_DEFICIT, f"expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}, saw " f"{sub_slot.reward_chain.deficit}", ), ) else: # 2t. Otherwise, deficit stays the same at the slot ends, cannot reset until 0 if sub_slot.reward_chain.deficit != prev_sb.deficit: return None, ValidationError( Err.INVALID_DEFICIT, "deficit is wrong at slot end") # 3. Check sub-epoch summary # Note that the subepoch summary is the summary of the previous subepoch (not the one that just finished) if not skip_overflow_last_ss_validation: if ses_hash is not None: # 3a. Check that genesis block does not have sub-epoch summary if genesis_block: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH, "genesis with sub-epoch-summary hash", ), ) assert prev_sb is not None # 3b. Check that we finished a slot and we finished a sub-epoch if not new_sub_slot or not can_finish_se: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH, f"new sub-slot: {new_sub_slot} finishes sub-epoch {can_finish_se}", ), ) # 3c. Check the actual sub-epoch is correct expected_sub_epoch_summary = make_sub_epoch_summary( constants, sub_blocks, height, sub_blocks.sub_block_record(prev_sb.prev_hash), expected_difficulty if can_finish_epoch else None, expected_sub_slot_iters if can_finish_epoch else None, ) expected_hash = expected_sub_epoch_summary.get_hash() if expected_hash != ses_hash: log.error(f"{expected_sub_epoch_summary}") return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY, f"expected ses hash: {expected_hash} got {ses_hash} ", ), ) elif new_sub_slot and not genesis_block: # 3d. Check that we don't have to include a sub-epoch summary if can_finish_se or can_finish_epoch: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY, "block finishes sub-epoch but ses-hash is None", ), ) # 4. Check if the number of sub-blocks is less than the max if not new_sub_slot and not genesis_block: assert prev_sb is not None num_sub_blocks = 2 # This includes the current sub-block and the prev sub-block curr = prev_sb while not curr.first_in_sub_slot: num_sub_blocks += 1 curr = sub_blocks.sub_block_record(curr.prev_hash) if num_sub_blocks > constants.MAX_SUB_SLOT_SUB_BLOCKS: return None, ValidationError(Err.TOO_MANY_SUB_BLOCKS) # If sub_block state is correct, we should always find a challenge here # This computes what the challenge should be for this sub-block challenge = get_block_challenge( constants, header_block, sub_blocks, genesis_block, overflow, skip_overflow_last_ss_validation, ) # 5a. Check proof of space if challenge != header_block.reward_chain_sub_block.pos_ss_cc_challenge_hash: log.error(f"Finished slots: {header_block.finished_sub_slots}") log.error( f"Data: {genesis_block} {overflow} {skip_overflow_last_ss_validation} {header_block.total_iters} " f"{header_block.reward_chain_sub_block.signage_point_index}" f"Prev: {prev_sb}") log.error( f"Challenge {challenge} provided {header_block.reward_chain_sub_block.pos_ss_cc_challenge_hash}" ) return None, ValidationError(Err.INVALID_CC_CHALLENGE) # 5b. Check proof of space if header_block.reward_chain_sub_block.challenge_chain_sp_vdf is None: # Edge case of first sp (start of slot), where sp_iters == 0 cc_sp_hash: bytes32 = challenge else: cc_sp_hash = header_block.reward_chain_sub_block.challenge_chain_sp_vdf.output.get_hash( ) q_str: Optional[ bytes32] = header_block.reward_chain_sub_block.proof_of_space.verify_and_get_quality_string( constants, challenge, cc_sp_hash) if q_str is None: return None, ValidationError(Err.INVALID_POSPACE) # 6. check signage point index # no need to check negative values as this is uint 8 if header_block.reward_chain_sub_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: return None, ValidationError(Err.INVALID_SP_INDEX) # Note that required iters might be from the previous slot (if we are in an overflow sub-block) required_iters: uint64 = calculate_iterations_quality( q_str, header_block.reward_chain_sub_block.proof_of_space.size, expected_difficulty, cc_sp_hash, ) # 7. check signage point index # no need to check negative values as this is uint8. (Assumes types are checked) if header_block.reward_chain_sub_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: return None, ValidationError(Err.INVALID_SP_INDEX) # 8a. check signage point index 0 has no cc sp if (header_block.reward_chain_sub_block.signage_point_index == 0) != ( header_block.reward_chain_sub_block.challenge_chain_sp_vdf is None): return None, ValidationError(Err.INVALID_SP_INDEX) # 8b. check signage point index 0 has no rc sp if (header_block.reward_chain_sub_block.signage_point_index == 0) != ( header_block.reward_chain_sub_block.reward_chain_sp_vdf is None): return None, ValidationError(Err.INVALID_SP_INDEX) sp_iters: uint64 = calculate_sp_iters( constants, expected_sub_slot_iters, header_block.reward_chain_sub_block.signage_point_index, ) ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_sub_block.signage_point_index, required_iters, ) if header_block.reward_chain_sub_block.challenge_chain_sp_vdf is None: # Blocks with very low required iters are not overflow blocks assert not overflow # 9. Check no overflows in the first sub-slot of a new epoch # (although they are OK in the second sub-slot), this is important if overflow and can_finish_epoch: if finished_sub_slots_since_prev < 2: return None, ValidationError( Err.NO_OVERFLOWS_IN_FIRST_SUB_SLOT_NEW_EPOCH) # 10. Check total iters if genesis_block: total_iters: uint128 = uint128(expected_sub_slot_iters * finished_sub_slots_since_prev) else: assert prev_sb is not None if new_sub_slot: total_iters = prev_sb.total_iters # Add the rest of the slot of prev_sb total_iters = uint128(total_iters + prev_sb.sub_slot_iters - prev_sb.ip_iters(constants)) # Add other empty slots total_iters = uint128(total_iters + (expected_sub_slot_iters * (finished_sub_slots_since_prev - 1))) else: # Slot iters is guaranteed to be the same for header_block and prev_sb # This takes the beginning of the slot, and adds ip_iters total_iters = uint128(prev_sb.total_iters - prev_sb.ip_iters(constants)) total_iters = uint128(total_iters + ip_iters) if total_iters != header_block.reward_chain_sub_block.total_iters: return ( None, ValidationError( Err.INVALID_TOTAL_ITERS, f"expected {total_iters} got {header_block.reward_chain_sub_block.total_iters}", ), ) sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - ( expected_sub_slot_iters if overflow else 0)) if overflow and skip_overflow_last_ss_validation: dummy_vdf_info = VDFInfo( bytes32([0] * 32), uint64(1), ClassgroupElement.get_default_element(), ) dummy_sub_slot = EndOfSubSlotBundle( ChallengeChainSubSlot(dummy_vdf_info, None, None, None, None), None, RewardChainSubSlot(dummy_vdf_info, bytes32([0] * 32), None, uint8(0)), SubSlotProofs(VDFProof(uint8(0), b""), None, VDFProof(uint8(0), b"")), ) sub_slots_to_pass_in = header_block.finished_sub_slots + [ dummy_sub_slot ] else: sub_slots_to_pass_in = header_block.finished_sub_slots ( cc_vdf_challenge, rc_vdf_challenge, cc_vdf_input, rc_vdf_input, cc_vdf_iters, rc_vdf_iters, ) = get_signage_point_vdf_info( constants, sub_slots_to_pass_in, overflow, prev_sb, sub_blocks, sp_total_iters, sp_iters, ) # 11. Check reward chain sp proof if sp_iters != 0: assert (header_block.reward_chain_sub_block.reward_chain_sp_vdf is not None and header_block.reward_chain_sp_proof is not None) target_vdf_info = VDFInfo( rc_vdf_challenge, rc_vdf_iters, header_block.reward_chain_sub_block.reward_chain_sp_vdf.output, ) if not skip_vdf_is_valid and not header_block.reward_chain_sp_proof.is_valid( constants, rc_vdf_input, header_block.reward_chain_sub_block.reward_chain_sp_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_SP_VDF) rc_sp_hash = header_block.reward_chain_sub_block.reward_chain_sp_vdf.output.get_hash( ) else: # Edge case of first sp (start of slot), where sp_iters == 0 assert overflow is not None if header_block.reward_chain_sub_block.reward_chain_sp_vdf is not None: return None, ValidationError(Err.INVALID_RC_SP_VDF) if new_sub_slot: rc_sp_hash = header_block.finished_sub_slots[ -1].reward_chain.get_hash() else: if genesis_block: rc_sp_hash = constants.GENESIS_CHALLENGE else: assert prev_sb is not None curr = prev_sb while not curr.first_in_sub_slot: curr = sub_blocks.sub_block_record(curr.prev_hash) assert curr.finished_reward_slot_hashes is not None rc_sp_hash = curr.finished_reward_slot_hashes[-1] # 12. Check reward chain sp signature if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space.plot_public_key, rc_sp_hash, header_block.reward_chain_sub_block.reward_chain_sp_signature, ): return None, ValidationError(Err.INVALID_RC_SIGNATURE) # 13. Check cc sp vdf if sp_iters != 0: assert header_block.reward_chain_sub_block.challenge_chain_sp_vdf is not None assert header_block.challenge_chain_sp_proof is not None target_vdf_info = VDFInfo( cc_vdf_challenge, cc_vdf_iters, header_block.reward_chain_sub_block.challenge_chain_sp_vdf.output, ) if header_block.reward_chain_sub_block.challenge_chain_sp_vdf != dataclasses.replace( target_vdf_info, number_of_iterations=sp_iters, ): return None, ValidationError(Err.INVALID_CC_SP_VDF) if not skip_vdf_is_valid and not header_block.challenge_chain_sp_proof.is_valid( constants, cc_vdf_input, target_vdf_info, None): return None, ValidationError(Err.INVALID_CC_SP_VDF) else: assert overflow is not None if header_block.reward_chain_sub_block.challenge_chain_sp_vdf is not None: return None, ValidationError(Err.INVALID_CC_SP_VDF) # 14. Check cc sp sig if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space.plot_public_key, cc_sp_hash, header_block.reward_chain_sub_block.challenge_chain_sp_signature, ): return None, ValidationError(Err.INVALID_CC_SIGNATURE, "invalid cc sp sig") # 15. Check is_block if genesis_block: if header_block.foliage_sub_block.foliage_block_hash is None: return None, ValidationError(Err.INVALID_IS_BLOCK, "invalid genesis") else: assert prev_sb is not None # Finds the previous block curr = prev_sb while not curr.is_block: curr = sub_blocks.sub_block_record(curr.prev_hash) # The first sub-block to have an sp > the last block's infusion iters, is a block if overflow: our_sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - expected_sub_slot_iters) else: our_sp_total_iters = uint128(total_iters - ip_iters + sp_iters) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage_sub_block.foliage_block_hash is not None): return None, ValidationError(Err.INVALID_IS_BLOCK) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage_sub_block.foliage_block_signature is not None): return None, ValidationError(Err.INVALID_IS_BLOCK) # 16. Check foliage sub block signature by plot key if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space.plot_public_key, header_block.foliage_sub_block.foliage_sub_block_data.get_hash(), header_block.foliage_sub_block.foliage_sub_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 17. Check foliage block signature by plot key if header_block.foliage_sub_block.foliage_block_hash is not None: if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space. plot_public_key, header_block.foliage_sub_block.foliage_block_hash, header_block.foliage_sub_block.foliage_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 18. Check unfinished reward chain sub block hash if (header_block.reward_chain_sub_block.get_hash() != header_block.foliage_sub_block.foliage_sub_block_data. unfinished_reward_block_hash): return None, ValidationError(Err.INVALID_URSB_HASH) # 19. Check pool target max height if (header_block.foliage_sub_block.foliage_sub_block_data.pool_target. max_height != 0 and header_block.foliage_sub_block. foliage_sub_block_data.pool_target.max_height < height): return None, ValidationError(Err.OLD_POOL_TARGET) # 20a. Check pre-farm puzzle hashes for genesis sub-block. if genesis_block: if (header_block.foliage_sub_block.foliage_sub_block_data.pool_target. puzzle_hash != constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH): log.error( f"Pool target {header_block.foliage_sub_block.foliage_sub_block_data.pool_target} hb {header_block}" ) return None, ValidationError(Err.INVALID_PREFARM) if (header_block.foliage_sub_block.foliage_sub_block_data. farmer_reward_puzzle_hash != constants.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH): return None, ValidationError(Err.INVALID_PREFARM) else: # 20b. Check pool target signature. Should not check this for genesis sub-block. if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space. pool_public_key, bytes(header_block.foliage_sub_block.foliage_sub_block_data. pool_target), header_block.foliage_sub_block.foliage_sub_block_data. pool_signature, ): return None, ValidationError(Err.INVALID_POOL_SIGNATURE) # 21. Check extension data if applicable. None for mainnet. # 22. Check if foliage block is present if (header_block.foliage_sub_block.foliage_block_hash is not None) != (header_block.foliage_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if (header_block.foliage_sub_block.foliage_block_signature is not None) != (header_block.foliage_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if header_block.foliage_block is not None: # 23. Check foliage block hash if header_block.foliage_block.get_hash( ) != header_block.foliage_sub_block.foliage_block_hash: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH) if genesis_block: # 24a. Check prev block hash for genesis if header_block.foliage_block.prev_block_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) else: assert prev_sb is not None # 24b. Check prev block hash for non-genesis curr_sb: SubBlockRecord = prev_sb while not curr_sb.is_block: curr_sb = sub_blocks.sub_block_record(curr_sb.prev_hash) if not header_block.foliage_block.prev_block_hash == curr_sb.header_hash: log.error( f"Prev BH: {header_block.foliage_block.prev_block_hash} {curr_sb.header_hash} curr sb: {curr_sb}" ) return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) # 25. The filter hash in the Foliage Block must be the hash of the filter if check_filter: if header_block.foliage_block.filter_hash != std_hash( header_block.transactions_filter): return None, ValidationError( Err.INVALID_TRANSACTIONS_FILTER_HASH) # 26. The timestamp in Foliage Block must comply with the timestamp rules if prev_sb is not None: last_timestamps: List[uint64] = [] curr_sb = sub_blocks.sub_block_record( header_block.foliage_block.prev_block_hash) assert curr_sb.timestamp is not None while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS: last_timestamps.append(curr_sb.timestamp) fetched: Optional[SubBlockRecord] = sub_blocks.try_sub_block( curr_sb.prev_block_hash) if not fetched: break curr_sb = fetched if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS: # For blocks 1 to 10, average timestamps of all previous blocks assert curr_sb.height == 0 prev_time: uint64 = uint64( int(sum(last_timestamps) // len(last_timestamps))) if header_block.foliage_block.timestamp <= prev_time: return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_PAST) if header_block.foliage_block.timestamp > int( time.time() + constants.MAX_FUTURE_TIME): return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_FUTURE) return required_iters, None # Valid unfinished header block
def get_consecutive_blocks( self, num_blocks: int, block_list_input: List[FullBlock] = None, farmer_reward_puzzle_hash: Optional[bytes32] = None, pool_reward_puzzle_hash: Optional[bytes32] = None, transaction_data: Optional[SpendBundle] = None, seed: bytes = b"", time_per_sub_block: Optional[float] = None, force_overflow: bool = False, skip_slots: int = 0, # Force at least this number of empty slots before the first SB guarantee_block: bool = False, # Force that this sub-block must be a block ) -> List[FullBlock]: assert num_blocks > 0 if block_list_input is not None: block_list = block_list_input.copy() else: block_list = [] constants = self.constants transaction_data_included = False if time_per_sub_block is None: time_per_sub_block = float(constants.SUB_SLOT_TIME_TARGET) / float(constants.SLOT_SUB_BLOCKS_TARGET) if farmer_reward_puzzle_hash is None: farmer_reward_puzzle_hash = self.farmer_ph if pool_reward_puzzle_hash is None: pool_reward_puzzle_hash = self.pool_ph pool_target = PoolTarget(pool_reward_puzzle_hash, uint32(0)) if len(block_list) == 0: initial_block_list_len = 0 genesis = self.create_genesis_block( constants, seed, force_overflow=force_overflow, skip_slots=skip_slots, timestamp=uint64(int(time.time())), farmer_reward_puzzle_hash=farmer_reward_puzzle_hash, ) log.info(f"Created block 0 iters: {genesis.total_iters}") num_empty_slots_added = skip_slots block_list = [genesis] num_blocks -= 1 else: initial_block_list_len = len(block_list) num_empty_slots_added = uint32(0) # Allows forcing empty slots in the beginning, for testing purposes if num_blocks == 0: return block_list height_to_hash, difficulty, sub_blocks = load_block_list(block_list, constants) latest_sub_block: SubBlockRecord = sub_blocks[block_list[-1].header_hash] curr = latest_sub_block while not curr.is_block: curr = sub_blocks[curr.prev_hash] start_timestamp = curr.timestamp start_height = curr.sub_block_height curr = latest_sub_block sub_blocks_added_this_sub_slot = 1 while not curr.first_in_sub_slot: curr = sub_blocks[curr.prev_hash] sub_blocks_added_this_sub_slot += 1 finished_sub_slots_at_sp: List[EndOfSubSlotBundle] = [] # Sub-slots since last sub block, up to signage point finished_sub_slots_at_ip: List[EndOfSubSlotBundle] = [] # Sub-slots since last sub block, up to infusion point sub_slot_iters: uint64 = latest_sub_block.sub_slot_iters # The number of iterations in one sub-slot same_slot_as_last = True # Only applies to first slot, to prevent old blocks from being added sub_slot_start_total_iters: uint128 = latest_sub_block.ip_sub_slot_total_iters(constants) sub_slots_finished = 0 pending_ses: bool = False # Start at the last block in block list # Get the challenge for that slot while True: slot_cc_challenge, slot_rc_challenge = get_challenges( constants, sub_blocks, finished_sub_slots_at_sp, latest_sub_block.header_hash, ) prev_num_of_blocks = num_blocks if num_empty_slots_added < skip_slots: # If did not reach the target slots to skip, don't make any proofs for this sub-slot num_empty_slots_added += 1 else: # Loop over every signage point (Except for the last ones, which are used for overflows) for signage_point_index in range(0, constants.NUM_SPS_SUB_SLOT - constants.NUM_SP_INTERVALS_EXTRA): curr = latest_sub_block while curr.total_iters > sub_slot_start_total_iters + calculate_sp_iters( constants, sub_slot_iters, uint8(signage_point_index) ): if curr.sub_block_height == 0: break curr = sub_blocks[curr.prev_hash] if curr.total_iters > sub_slot_start_total_iters: finished_sub_slots_at_sp = [] if same_slot_as_last: if signage_point_index < latest_sub_block.signage_point_index: # Ignore this signage_point because it's in the past continue signage_point: SignagePoint = get_signage_point( constants, sub_blocks, latest_sub_block, sub_slot_start_total_iters, uint8(signage_point_index), finished_sub_slots_at_sp, sub_slot_iters, ) if signage_point_index == 0: cc_sp_output_hash: bytes32 = slot_cc_challenge else: assert signage_point.cc_vdf is not None cc_sp_output_hash = signage_point.cc_vdf.output.get_hash() qualified_proofs: List[Tuple[uint64, ProofOfSpace]] = self.get_pospaces_for_challenge( constants, slot_cc_challenge, cc_sp_output_hash, seed, difficulty, sub_slot_iters, ) for required_iters, proof_of_space in sorted(qualified_proofs, key=lambda t: t[0]): if sub_blocks_added_this_sub_slot == constants.MAX_SUB_SLOT_SUB_BLOCKS or force_overflow: break if same_slot_as_last: if signage_point_index == latest_sub_block.signage_point_index: # Ignore this sub-block because it's in the past if required_iters <= latest_sub_block.required_iters: continue assert latest_sub_block.header_hash in sub_blocks if transaction_data_included: transaction_data = None assert start_timestamp is not None full_block, sub_block_record = get_full_block_and_sub_record( constants, sub_blocks, sub_slot_start_total_iters, uint8(signage_point_index), proof_of_space, slot_cc_challenge, slot_rc_challenge, farmer_reward_puzzle_hash, pool_target, start_timestamp, start_height, time_per_sub_block, transaction_data, height_to_hash, difficulty, required_iters, sub_slot_iters, self.get_plot_signature, self.get_pool_key_signature, finished_sub_slots_at_ip, signage_point, latest_sub_block, seed, ) if sub_block_record.is_block: transaction_data_included = True else: if guarantee_block: continue if pending_ses: pending_ses = False block_list.append(full_block) sub_blocks_added_this_sub_slot += 1 sub_blocks[full_block.header_hash] = sub_block_record log.info( f"Created block {sub_block_record.sub_block_height} ove=False, iters " f"{sub_block_record.total_iters}" ) height_to_hash[uint32(full_block.sub_block_height)] = full_block.header_hash latest_sub_block = sub_blocks[full_block.header_hash] finished_sub_slots_at_ip = [] num_blocks -= 1 if num_blocks == 0: return block_list # Finish the end of sub-slot and try again next sub-slot # End of sub-slot logic if len(finished_sub_slots_at_ip) == 0: # Sub block has been created within this sub-slot eos_iters: uint64 = uint64(sub_slot_iters - (latest_sub_block.total_iters - sub_slot_start_total_iters)) cc_input: ClassgroupElement = latest_sub_block.challenge_vdf_output rc_challenge: bytes32 = latest_sub_block.reward_infusion_new_challenge else: # No sub-blocks were successfully created within this sub-slot eos_iters = sub_slot_iters cc_input = ClassgroupElement.get_default_element() rc_challenge = slot_rc_challenge cc_vdf, cc_proof = get_vdf_info_and_proof( constants, cc_input, slot_cc_challenge, eos_iters, ) rc_vdf, rc_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), rc_challenge, eos_iters, ) eos_deficit: uint8 = ( latest_sub_block.deficit if latest_sub_block.deficit > 0 else constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK ) icc_ip_vdf, icc_ip_proof = get_icc( constants, uint128(sub_slot_start_total_iters + sub_slot_iters), finished_sub_slots_at_ip, latest_sub_block, sub_blocks, sub_slot_start_total_iters, eos_deficit, ) # End of slot vdf info for icc and cc have to be from challenge block or start of slot, respectively, # in order for light clients to validate. cc_vdf = VDFInfo(cc_vdf.challenge, sub_slot_iters, cc_vdf.output) if pending_ses: sub_epoch_summary: Optional[SubEpochSummary] = None else: sub_epoch_summary = next_sub_epoch_summary( constants, sub_blocks, height_to_hash, latest_sub_block.required_iters, block_list[-1], False, ) pending_ses = True if sub_epoch_summary is not None: ses_hash = sub_epoch_summary.get_hash() new_sub_slot_iters: Optional[uint64] = sub_epoch_summary.new_sub_slot_iters new_difficulty: Optional[uint64] = sub_epoch_summary.new_difficulty log.info(f"Sub epoch summary: {sub_epoch_summary}") else: ses_hash = None new_sub_slot_iters = None new_difficulty = None if icc_ip_vdf is not None: # Icc vdf (Deficit of latest sub-block is <= 4) if len(finished_sub_slots_at_ip) == 0: # This means there are sub-blocks in this sub-slot curr = latest_sub_block while not curr.is_challenge_sub_block(constants) and not curr.first_in_sub_slot: curr = sub_blocks[curr.prev_hash] if curr.is_challenge_sub_block(constants): icc_eos_iters = uint64(sub_slot_start_total_iters + sub_slot_iters - curr.total_iters) else: icc_eos_iters = sub_slot_iters else: # This means there are no sub-blocks in this sub-slot icc_eos_iters = sub_slot_iters icc_ip_vdf = VDFInfo( icc_ip_vdf.challenge, icc_eos_iters, icc_ip_vdf.output, ) icc_sub_slot: Optional[InfusedChallengeChainSubSlot] = InfusedChallengeChainSubSlot(icc_ip_vdf) assert icc_sub_slot is not None icc_sub_slot_hash = icc_sub_slot.get_hash() if latest_sub_block.deficit == 0 else None cc_sub_slot = ChallengeChainSubSlot( cc_vdf, icc_sub_slot_hash, ses_hash, new_sub_slot_iters, new_difficulty, ) else: # No icc icc_sub_slot = None cc_sub_slot = ChallengeChainSubSlot(cc_vdf, None, ses_hash, new_sub_slot_iters, new_difficulty) finished_sub_slots_at_ip.append( EndOfSubSlotBundle( cc_sub_slot, icc_sub_slot, RewardChainSubSlot( rc_vdf, cc_sub_slot.get_hash(), icc_sub_slot.get_hash() if icc_sub_slot is not None else None, eos_deficit, ), SubSlotProofs(cc_proof, icc_ip_proof, rc_proof), ) ) finished_sub_slots_eos = finished_sub_slots_at_ip.copy() latest_sub_block_eos = latest_sub_block overflow_cc_challenge = finished_sub_slots_at_ip[-1].challenge_chain.get_hash() overflow_rc_challenge = finished_sub_slots_at_ip[-1].reward_chain.get_hash() if transaction_data_included: transaction_data = None sub_slots_finished += 1 log.info( f"Sub slot finished. Sub-blocks included: {sub_blocks_added_this_sub_slot} sub_blocks_per_slot: " f"{(len(block_list) - initial_block_list_len)/sub_slots_finished}" ) sub_blocks_added_this_sub_slot = 0 # Sub slot ended, overflows are in next sub slot # Handle overflows: No overflows on new epoch if new_sub_slot_iters is None and num_empty_slots_added >= skip_slots and new_difficulty is None: for signage_point_index in range( constants.NUM_SPS_SUB_SLOT - constants.NUM_SP_INTERVALS_EXTRA, constants.NUM_SPS_SUB_SLOT, ): # note that we are passing in the finished slots which include the last slot signage_point = get_signage_point( constants, sub_blocks, latest_sub_block_eos, sub_slot_start_total_iters, uint8(signage_point_index), finished_sub_slots_eos, sub_slot_iters, ) if signage_point_index == 0: cc_sp_output_hash = slot_cc_challenge else: assert signage_point is not None assert signage_point.cc_vdf is not None cc_sp_output_hash = signage_point.cc_vdf.output.get_hash() # If did not reach the target slots to skip, don't make any proofs for this sub-slot qualified_proofs = self.get_pospaces_for_challenge( constants, slot_cc_challenge, cc_sp_output_hash, seed, difficulty, sub_slot_iters, ) for required_iters, proof_of_space in sorted(qualified_proofs, key=lambda t: t[0]): if sub_blocks_added_this_sub_slot == constants.MAX_SUB_SLOT_SUB_BLOCKS: break assert start_timestamp is not None full_block, sub_block_record = get_full_block_and_sub_record( constants, sub_blocks, sub_slot_start_total_iters, uint8(signage_point_index), proof_of_space, slot_cc_challenge, slot_rc_challenge, farmer_reward_puzzle_hash, pool_target, start_timestamp, start_height, time_per_sub_block, transaction_data, height_to_hash, difficulty, required_iters, sub_slot_iters, self.get_plot_signature, self.get_pool_key_signature, finished_sub_slots_at_ip, signage_point, latest_sub_block, seed, overflow_cc_challenge=overflow_cc_challenge, overflow_rc_challenge=overflow_rc_challenge, ) if sub_block_record.is_block: transaction_data_included = True elif guarantee_block: continue if pending_ses: pending_ses = False block_list.append(full_block) sub_blocks_added_this_sub_slot += 1 log.info( f"Created block {sub_block_record.sub_block_height } ov=True, iters " f"{sub_block_record.total_iters}" ) num_blocks -= 1 if num_blocks == 0: return block_list sub_blocks[full_block.header_hash] = sub_block_record height_to_hash[uint32(full_block.sub_block_height)] = full_block.header_hash latest_sub_block = sub_blocks[full_block.header_hash] finished_sub_slots_at_ip = [] finished_sub_slots_at_sp = finished_sub_slots_eos.copy() same_slot_as_last = False sub_slot_start_total_iters = uint128(sub_slot_start_total_iters + sub_slot_iters) if num_blocks < prev_num_of_blocks: num_empty_slots_added += 1 if new_sub_slot_iters is not None: assert new_difficulty is not None sub_slot_iters = new_sub_slot_iters difficulty = new_difficulty
async def _do_process_communication( self, chain: Chain, challenge: bytes32, initial_form: ClassgroupElement, ip: str, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, ): disc: int = create_discriminant(challenge, self.constants.DISCRIMINANT_SIZE_BITS) try: # Depending on the flags 'fast_algorithm' and 'sanitizer_mode', # the timelord tells the vdf_client what to execute. async with self.lock: if self.config["fast_algorithm"]: # Run n-wesolowski (fast) algorithm. writer.write(b"N") else: # Run two-wesolowski (slow) algorithm. writer.write(b"T") await writer.drain() prefix = str(len(str(disc))) if len(prefix) == 1: prefix = "00" + prefix if len(prefix) == 2: prefix = "0" + prefix async with self.lock: writer.write((prefix + str(disc)).encode()) await writer.drain() # Send (a, b) from 'initial_form'. for num in [initial_form.a, initial_form.b]: prefix_l = len(str(num)) prefix_len = len(str(prefix_l)) async with self.lock: writer.write( (str(prefix_len) + str(prefix_l) + str(num)).encode()) await writer.drain() try: ok = await reader.readexactly(2) except (asyncio.IncompleteReadError, ConnectionResetError, Exception) as e: log.warning(f"{type(e)} {e}") async with self.lock: self.vdf_failures.append(chain) self.vdf_failures_count += 1 return if ok.decode() != "OK": return log.info("Got handshake with VDF client.") async with self.lock: self.allows_iters.append(chain) # Listen to the client until "STOP" is received. while True: try: data = await reader.readexactly(4) except ( asyncio.IncompleteReadError, ConnectionResetError, Exception, ) as e: log.warning(f"{type(e)} {e}") async with self.lock: self.vdf_failures.append(chain) self.vdf_failures_count += 1 break msg = "" try: msg = data.decode() except Exception: pass if msg == "STOP": log.info(f"Stopped client running on ip {ip}.") async with self.lock: writer.write(b"ACK") await writer.drain() break else: try: # This must be a proof, 4 bytes is length prefix length = int.from_bytes(data, "big") proof = await reader.readexactly(length) stdout_bytes_io: io.BytesIO = io.BytesIO( bytes.fromhex(proof.decode())) except ( asyncio.IncompleteReadError, ConnectionResetError, Exception, ) as e: log.warning(f"{type(e)} {e}") async with self.lock: self.vdf_failures.append(chain) self.vdf_failures_count += 1 break iterations_needed = uint64( int.from_bytes(stdout_bytes_io.read(8), "big", signed=True)) y_size_bytes = stdout_bytes_io.read(8) y_size = uint64( int.from_bytes(y_size_bytes, "big", signed=True)) y_bytes = stdout_bytes_io.read(y_size) witness_type = uint8( int.from_bytes(stdout_bytes_io.read(1), "big", signed=True)) proof_bytes: bytes = stdout_bytes_io.read() # Verifies our own proof just in case int_size = (self.constants.DISCRIMINANT_SIZE_BITS + 16) >> 4 a = int.from_bytes(y_bytes[:int_size], "big", signed=True) b = int.from_bytes(y_bytes[int_size:], "big", signed=True) output = ClassgroupElement(int512(a), int512(b)) time_taken = time.time() - self.chain_start_time[chain] ips = int(iterations_needed / time_taken * 10) / 10 log.info( f"Finished PoT chall:{challenge[:10].hex()}.. {iterations_needed}" f" iters, " f"Estimated IPS: {ips}, Chain: {chain}") vdf_info: VDFInfo = VDFInfo( challenge, iterations_needed, output, ) vdf_proof: VDFProof = VDFProof( witness_type, proof_bytes, ) if not vdf_proof.is_valid(self.constants, initial_form, vdf_info): log.error("Invalid proof of time!") async with self.lock: self.proofs_finished.append( (chain, vdf_info, vdf_proof)) except ConnectionResetError as e: log.info(f"Connection reset with VDF client {e}")
def new_signage_point( self, index: uint8, sub_blocks: Dict[bytes32, SubBlockRecord], peak: Optional[SubBlockRecord], next_sub_slot_iters: uint64, signage_point: SignagePoint, ) -> bool: """ Returns true if sp successfully added """ assert len(self.finished_sub_slots) >= 1 if peak is None or peak.sub_block_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.FIRST_CC_CHALLENGE ss_reward_hash = self.constants.FIRST_RC_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 sub-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 sub-block where it's iters are before our sp_total_iters, in this ss check_from_start_of_ss = True break curr = sub_blocks[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 == replace( cc_vdf_info_expected, number_of_iterations=delta_iters): 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 signage_point.cc_proof.is_valid( self.constants, start_ele, cc_vdf_info_expected, ): return False if rc_vdf_info_expected.challenge != signage_point.rc_vdf.challenge: # This signage point is probably outdated return False if not signage_point.rc_proof.is_valid( self.constants, ClassgroupElement.get_default_element(), signage_point.rc_vdf, rc_vdf_info_expected, ): return False sp_arr[index] = signage_point return True return False