def _can_infuse_unfinished_block( self, block: timelord_protocol.NewUnfinishedBlock) -> 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: 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
def test_is_overflow_block(self): assert not is_overflow_block(test_constants, uint8(27)) assert not is_overflow_block(test_constants, uint8(28)) assert is_overflow_block(test_constants, uint8(29)) assert is_overflow_block(test_constants, uint8(30)) assert is_overflow_block(test_constants, uint8(31)) with raises(ValueError): assert is_overflow_block(test_constants, uint8(32))
async def pre_validate_blocks_multiprocessing( constants: ConsensusConstants, constants_json: Dict, block_records: BlockchainInterface, blocks: Sequence[Union[FullBlock, HeaderBlock]], pool: ProcessPoolExecutor, validate_transactions: bool, check_filter: bool, ) -> 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: validate_transactions: constants_json: pool: constants: block_records: blocks: list of full blocks to validate (must be connected to current chain) """ batch_size = 4 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 and 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) if block.reward_chain_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: log.warning(f"Block: {block.reward_chain_block}") 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, ) recent_blocks[block_rec.header_hash] = block_rec recent_blocks_compressed[block_rec.header_hash] = block_rec block_records.add_block_record( block_rec) # Temporarily add block to dict prev_b = block_rec diff_ssis.append((difficulty, sub_slot_iters)) for i, block in enumerate(blocks): 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() } 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 hb_pickled: List[bytes] = [] generators: List[Optional[bytes]] = [] for block in blocks_to_validate: if isinstance(block, FullBlock): hb_pickled.append(bytes(block.get_block_header())) generators.append( bytes(block.transactions_generator) if block. transactions_generator is not None else None) else: hb_pickled.append(bytes(block)) generators.append(None) futures.append(asyncio.get_running_loop().run_in_executor( pool, batch_pre_validate_blocks, constants_json, final_pickled, hb_pickled, generators, check_filter, [diff_ssis[j][0] for j in range(i, end_i)], [diff_ssis[j][1] for j in range(i, end_i)], validate_transactions, )) # 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 validate_finished_header_block( constants: ConsensusConstants, 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 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_block.get_unfinished(), header_block.challenge_chain_sp_proof, header_block.reward_chain_sp_proof, header_block.foliage, header_block.foliage_transaction_block, header_block.transactions_filter, ) required_iters, validate_unfinished_err = validate_unfinished_header_block( constants, 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_b: Optional[BlockRecord] = None genesis_block = True else: prev_b = blocks.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_block.signage_point_index, required_iters, ) if not genesis_block: assert prev_b is not None # 27. Check block height if header_block.height != prev_b.height + 1: return None, ValidationError(Err.INVALID_HEIGHT) # 28. Check weight if header_block.weight != prev_b.weight + expected_difficulty: log.error( f"INVALID WEIGHT: {header_block} {prev_b} {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_b 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_b.reward_infusion_new_challenge ip_vdf_iters = uint64(header_block.reward_chain_block.total_iters - prev_b.total_iters) cc_vdf_output = prev_b.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 block in slot if genesis_block: # genesis block cc_vdf_challenge = constants.GENESIS_CHALLENGE else: assert prev_b is not None # Not genesis block, go back to first block in slot curr = prev_b while curr.finished_challenge_slot_hashes is None: curr = blocks.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_block.challenge_chain_ip_vdf.output, ) if header_block.reward_chain_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_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_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_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_block( constants, header_block.reward_chain_block.signage_point_index) deficit = calculate_deficit( constants, header_block.height, prev_b, overflow, len(header_block.finished_sub_slots), ) if header_block.reward_chain_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_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_BLOCKS_PER_CHALLENGE_BLOCK - 1: return ( None, ValidationError( Err.INVALID_ICC_VDF, f"icc vdf and deficit is bigger or equal to {constants.MIN_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_b is not None if prev_b.is_challenge_block(constants): icc_vdf_input = ClassgroupElement.get_default_element() else: icc_vdf_input = prev_b.infused_challenge_vdf_output curr = prev_b while curr.finished_infused_challenge_slot_hashes is None and not curr.is_challenge_block( constants): curr = blocks.block_record(curr.prev_hash) if curr.is_challenge_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_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_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.reward_block_hash != header_block.reward_chain_block.get_hash( ): return None, ValidationError(Err.INVALID_REWARD_BLOCK_HASH) # 33. Check reward block is_transaction_block if (header_block.foliage.foliage_transaction_block_hash is not None ) != header_block.reward_chain_block.is_transaction_block: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) return required_iters, None
def validate_unfinished_header_block( constants: ConsensusConstants, 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 block. However, the finished_sub_slots refers to all sub-slots that were finishes from the previous block's infusion point, up to this blocks infusion point. Therefore, in the case where this is an overflow 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_b = blocks.try_block_record(header_block.prev_header_hash) genesis_block = prev_b 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_block( constants, header_block.reward_chain_block.signage_point_index) if skip_overflow_last_ss_validation and overflow: if final_eos_is_already_included(header_block, blocks, expected_sub_slot_iters): skip_overflow_last_ss_validation = False finished_sub_slots_since_prev = len( header_block.finished_sub_slots) else: 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_b is not None height = uint32(prev_b.height + 1) if prev_b.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_b.height, prev_b.deficit, blocks, prev_b.prev_hash, False, ) else: can_finish_se = False can_finish_epoch = False # 2. Check finished slots that have been crossed since prev_b 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 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_b is not None curr: BlockRecord = prev_b while not curr.first_in_sub_slot: curr = blocks.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_b 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_b.deficit < constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: # There should be no ICC chain if the last 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_b while not curr.is_challenge_block( constants) and not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) if curr.is_challenge_block(constants): icc_challenge_hash = curr.challenge_block_info_hash icc_iters_committed = uint64( prev_b.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_b.sub_slot_iters icc_iters_proof = uint64(prev_b.sub_slot_iters - prev_b.ip_iters(constants)) if prev_b.is_challenge_block(constants): icc_vdf_input = ClassgroupElement.get_default_element( ) else: icc_vdf_input = prev_b.infused_challenge_vdf_output else: # This is not the first sub slot after the last block, so we might not have an ICC if (header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.deficit < constants.MIN_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_b.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_b 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_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_b 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_b.reward_infusion_new_challenge eos_vdf_iters = uint64(prev_b.sub_slot_iters - prev_b.ip_iters(constants)) cc_start_element = prev_b.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_b is not None if finished_sub_slot_n == 0: cc_eos_vdf_info_iters = prev_b.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_BLOCKS_PER_CHALLENGE_BLOCK: return ( None, ValidationError( Err.INVALID_DEFICIT, f"genesis, expected deficit {constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK}", ), ) else: assert prev_b is not None if prev_b.deficit == 0: # 2s. If prev sb had deficit 0, resets deficit to MIN_BLOCK_PER_CHALLENGE_BLOCK if sub_slot.reward_chain.deficit != constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: log.error(constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK, ) return ( None, ValidationError( Err.INVALID_DEFICIT, f"expected deficit {constants.MIN_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_b.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_b 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, blocks, height, blocks.block_record(prev_b.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 blocks is less than the max if not new_sub_slot and not genesis_block: assert prev_b is not None num_blocks = 2 # This includes the current block and the prev block curr = prev_b while not curr.first_in_sub_slot: num_blocks += 1 curr = blocks.block_record(curr.prev_hash) if num_blocks > constants.MAX_SUB_SLOT_BLOCKS: return None, ValidationError(Err.TOO_MANY_BLOCKS) # If block state is correct, we should always find a challenge here # This computes what the challenge should be for this block challenge = get_block_challenge( constants, header_block, blocks, genesis_block, overflow, skip_overflow_last_ss_validation, ) # 5a. Check proof of space if challenge != header_block.reward_chain_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_block.signage_point_index}" f"Prev: {prev_b}") log.error( f"Challenge {challenge} provided {header_block.reward_chain_block.pos_ss_cc_challenge_hash}" ) return None, ValidationError(Err.INVALID_CC_CHALLENGE) # 5b. Check proof of space if header_block.reward_chain_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_block.challenge_chain_sp_vdf.output.get_hash( ) q_str: Optional[ bytes32] = header_block.reward_chain_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_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 block) required_iters: uint64 = calculate_iterations_quality( constants.DIFFICULTY_CONSTANT_FACTOR, q_str, header_block.reward_chain_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_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_block.signage_point_index == 0) != ( header_block.reward_chain_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_block.signage_point_index == 0) != ( header_block.reward_chain_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_block.signage_point_index, ) ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_block.signage_point_index, required_iters, ) if header_block.reward_chain_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_b is not None if new_sub_slot: total_iters = prev_b.total_iters # Add the rest of the slot of prev_b total_iters = uint128(total_iters + prev_b.sub_slot_iters - prev_b.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_b # This takes the beginning of the slot, and adds ip_iters total_iters = uint128(prev_b.total_iters - prev_b.ip_iters(constants)) total_iters = uint128(total_iters + ip_iters) if total_iters != header_block.reward_chain_block.total_iters: return ( None, ValidationError( Err.INVALID_TOTAL_ITERS, f"expected {total_iters} got {header_block.reward_chain_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_b, blocks, sp_total_iters, sp_iters, ) # 11. Check reward chain sp proof if sp_iters != 0: assert (header_block.reward_chain_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_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_block.reward_chain_sp_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_SP_VDF) rc_sp_hash = header_block.reward_chain_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_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_b is not None curr = prev_b 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] # 12. Check reward chain sp signature if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, rc_sp_hash, header_block.reward_chain_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_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_block.challenge_chain_sp_vdf.output, ) if header_block.reward_chain_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_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_block.proof_of_space.plot_public_key, cc_sp_hash, header_block.reward_chain_block.challenge_chain_sp_signature, ): return None, ValidationError(Err.INVALID_CC_SIGNATURE, "invalid cc sp sig") # 15. Check is_transaction_block if genesis_block: if header_block.foliage.foliage_transaction_block_hash is None: return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK, "invalid genesis") else: assert prev_b is not None # Finds the previous block curr = prev_b while not curr.is_transaction_block: curr = blocks.block_record(curr.prev_hash) # The first block to have an sp > the last tx block's infusion iters, is a tx 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.foliage_transaction_block_hash is not None): return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage.foliage_transaction_block_signature is not None): return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK) # 16. Check foliage block signature by plot key if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, header_block.foliage.foliage_block_data.get_hash(), header_block.foliage.foliage_block_data_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 17. Check foliage block signature by plot key if header_block.foliage.foliage_transaction_block_hash is not None: if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, header_block.foliage.foliage_transaction_block_hash, header_block.foliage.foliage_transaction_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 18. Check unfinished reward chain block hash if (header_block.reward_chain_block.get_hash() != header_block.foliage. foliage_block_data.unfinished_reward_block_hash): return None, ValidationError(Err.INVALID_URSB_HASH) # 19. Check pool target max height if (header_block.foliage.foliage_block_data.pool_target.max_height != 0 and header_block.foliage.foliage_block_data.pool_target.max_height < height): return None, ValidationError(Err.OLD_POOL_TARGET) # 20a. Check pre-farm puzzle hashes for genesis block. if genesis_block: if (header_block.foliage.foliage_block_data.pool_target.puzzle_hash != constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH): log.error( f"Pool target {header_block.foliage.foliage_block_data.pool_target} hb {header_block}" ) return None, ValidationError(Err.INVALID_PREFARM) if (header_block.foliage.foliage_block_data.farmer_reward_puzzle_hash != constants.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH): return None, ValidationError(Err.INVALID_PREFARM) else: # 20b. If pospace has a pool pk, heck pool target signature. Should not check this for genesis block. if header_block.reward_chain_block.proof_of_space.pool_public_key is not None: assert header_block.reward_chain_block.proof_of_space.pool_contract_puzzle_hash is None if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space. pool_public_key, bytes(header_block.foliage.foliage_block_data.pool_target), header_block.foliage.foliage_block_data.pool_signature, ): return None, ValidationError(Err.INVALID_POOL_SIGNATURE) else: # 20c. Otherwise, the plot is associated with a contract puzzle hash, not a public key assert header_block.reward_chain_block.proof_of_space.pool_contract_puzzle_hash is not None if (header_block.foliage.foliage_block_data.pool_target.puzzle_hash != header_block.reward_chain_block.proof_of_space. pool_contract_puzzle_hash): return None, ValidationError(Err.INVALID_POOL_TARGET) # 21. Check extension data if applicable. None for mainnet. # 22. Check if foliage block is present if (header_block.foliage.foliage_transaction_block_hash is not None) != (header_block.foliage_transaction_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if (header_block.foliage.foliage_transaction_block_signature is not None) != (header_block.foliage_transaction_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if header_block.foliage_transaction_block is not None: # 23. Check foliage block hash if header_block.foliage_transaction_block.get_hash( ) != header_block.foliage.foliage_transaction_block_hash: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH) if genesis_block: # 24a. Check prev block hash for genesis if header_block.foliage_transaction_block.prev_transaction_block_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) else: assert prev_b is not None # 24b. Check prev block hash for non-genesis curr_b: BlockRecord = prev_b while not curr_b.is_transaction_block: curr_b = blocks.block_record(curr_b.prev_hash) if not header_block.foliage_transaction_block.prev_transaction_block_hash == curr_b.header_hash: log.error( f"Prev BH: {header_block.foliage_transaction_block.prev_transaction_block_hash} " f"{curr_b.header_hash} curr sb: {curr_b}") 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_transaction_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_b is not None: last_timestamps: List[uint64] = [] curr_b = blocks.block_record( header_block.foliage_transaction_block. prev_transaction_block_hash) assert curr_b.timestamp is not None while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS: last_timestamps.append(curr_b.timestamp) fetched: Optional[BlockRecord] = blocks.try_block_record( curr_b.prev_transaction_block_hash) if not fetched: break curr_b = fetched if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS: # For blocks 1 to 10, average timestamps of all previous blocks assert curr_b.height == 0 prev_time: uint64 = uint64( int(sum(last_timestamps) // len(last_timestamps))) if header_block.foliage_transaction_block.timestamp <= prev_time: return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_PAST) if header_block.foliage_transaction_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 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, )
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( constants, blocks, prev_b.prev_hash, prev_b.height, prev_b.sub_slot_iters, prev_b.deficit, len(block.finished_sub_slots) > 0, prev_b.sp_total_iters(constants), ) overflow = is_overflow_block(constants, signage_point_index) 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, uint32(prev_b.height + 1), deficit, blocks, prev_b.header_hash if prev_b is not None else None, can_finish_soon, ) # can't finish se, no summary if not can_finish_se: return None next_difficulty = None next_sub_slot_iters = None # if can finish epoch, new difficulty and ssi if can_finish_epoch: sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index) ip_iters = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters) next_difficulty = get_next_difficulty( constants, blocks, block.prev_header_hash, uint32(prev_b.height + 1), uint64(prev_b.weight - blocks.block_record(prev_b.prev_hash).weight), deficit, True, uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)), True, ) next_sub_slot_iters = get_next_sub_slot_iters( constants, blocks, block.prev_header_hash, uint32(prev_b.height + 1), sub_slot_iters, deficit, True, uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)), True, ) return make_sub_epoch_summary( constants, blocks, uint32(prev_b.height + 2), prev_b, next_difficulty, next_sub_slot_iters, )
async def _check_for_new_ip(self): infusion_iters = [ iteration for iteration, t in self.iteration_to_proof_type.items() if t == IterationType.INFUSION_POINT ] for iteration in infusion_iters: proofs_with_iter = [ (chain, info, proof) for chain, info, proof in self.proofs_finished if info.number_of_iterations == iteration ] 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: continue if ip_iters - self.last_state.get_last_ip() == iteration: block = unfinished_block break 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 if rc_info.challenge != self.last_state.get_challenge(Chain.REWARD_CHAIN): log.warning( f"Do not have correct challenge {self.last_state.get_challenge(Chain.REWARD_CHAIN).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.unfinished_blocks.remove(block) self.total_infused += 1 log.info(f"Generated infusion point for challenge: {challenge} iterations: {iteration}.") if not self.last_state.can_infuse_block(): log.warning("Too many blocks, cannot infuse, discarding") # Too many blocks return overflow = is_overflow_block(self.constants, block.reward_chain_block.signage_point_index) 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 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: is_transaction_block = self.last_state.get_last_block_total_iters() < 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 new_reward_chain_block = RewardChainBlock( self.last_state.get_weight() + block.difficulty, height, 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.get_infused_sub_epoch_summary() is not None: new_sub_epoch_summary = None else: new_sub_epoch_summary = block.sub_epoch_summary self.new_peak = timelord_protocol.NewPeakTimelord( new_reward_chain_block, block.difficulty, new_deficit, block.sub_slot_iters, new_sub_epoch_summary, self.last_state.reward_challenge_cache, last_csb_or_eos, ) if self.total_unfinished > 0: infusion_rate = int(self.total_infused / self.total_unfinished * 100) log.info( f"Total unfinished blocks: {self.total_unfinished}." f"Total infused blocks: {self.total_infused}." f"Infusion rate: {infusion_rate}." ) await self._handle_new_peak() # Break so we alternate between checking SP and IP break
async def test_basic_store(self, empty_blockchain, normalized_to_identity: bool = False): blockchain = empty_blockchain blocks = bt.get_consecutive_blocks( 10, seed=b"1234", normalized_to_identity=normalized_to_identity) store = await FullNodeStore.create(test_constants) unfinished_blocks = [] for block in blocks: unfinished_blocks.append( UnfinishedBlock( block.finished_sub_slots, block.reward_chain_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_block, block.transactions_info, block.transactions_generator, )) # Add/get candidate block assert store.get_candidate_block( unfinished_blocks[0].get_hash()) is None for height, unf_block in enumerate(unfinished_blocks): store.add_candidate_block(unf_block.get_hash(), height, unf_block) assert store.get_candidate_block( unfinished_blocks[4].get_hash()) == unfinished_blocks[4] store.clear_candidate_blocks_below(uint32(8)) assert store.get_candidate_block( unfinished_blocks[5].get_hash()) is None assert store.get_candidate_block( unfinished_blocks[8].get_hash()) is not None # Test seen unfinished blocks h_hash_1 = bytes32(token_bytes(32)) assert not store.seen_unfinished_block(h_hash_1) assert store.seen_unfinished_block(h_hash_1) store.clear_seen_unfinished_blocks() assert not store.seen_unfinished_block(h_hash_1) # Add/get unfinished block for height, unf_block in enumerate(unfinished_blocks): assert store.get_unfinished_block(unf_block.partial_hash) is None store.add_unfinished_block( height, unf_block, PreValidationResult(None, uint64(123532), None)) assert store.get_unfinished_block( unf_block.partial_hash) == unf_block store.remove_unfinished_block(unf_block.partial_hash) assert store.get_unfinished_block(unf_block.partial_hash) is None blocks = bt.get_consecutive_blocks( 1, skip_slots=5, normalized_to_identity=normalized_to_identity) sub_slots = blocks[0].finished_sub_slots assert len(sub_slots) == 5 assert (store.get_finished_sub_slots( None, {}, sub_slots[0].challenge_chain.challenge_chain_end_of_slot_vdf. challenge, False, ) == []) # Test adding non-connecting sub-slots genesis assert store.get_sub_slot(test_constants.GENESIS_CHALLENGE) is None assert store.get_sub_slot( sub_slots[0].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[1].challenge_chain.get_hash()) is None assert store.new_finished_sub_slot(sub_slots[1], {}, None) is None assert store.new_finished_sub_slot(sub_slots[2], {}, None) is None # Test adding sub-slots after genesis assert store.new_finished_sub_slot(sub_slots[0], {}, None) is not None assert store.get_sub_slot( sub_slots[0].challenge_chain.get_hash())[0] == sub_slots[0] assert store.get_sub_slot( sub_slots[1].challenge_chain.get_hash()) is None assert store.new_finished_sub_slot(sub_slots[1], {}, None) is not None for i in range(len(sub_slots)): assert store.new_finished_sub_slot(sub_slots[i], {}, None) is not None assert store.get_sub_slot( sub_slots[i].challenge_chain.get_hash())[0] == sub_slots[i] assert store.get_finished_sub_slots( None, {}, sub_slots[-1].challenge_chain.get_hash(), False) == sub_slots with raises(ValueError): store.get_finished_sub_slots( None, {}, sub_slots[-1].challenge_chain.get_hash(), True) assert store.get_finished_sub_slots( None, {}, sub_slots[-2].challenge_chain.get_hash(), False) == sub_slots[:-1] # Test adding genesis peak await blockchain.receive_block(blocks[0]) peak = blockchain.get_peak() if peak.overflow: store.new_peak(peak, sub_slots[-2], sub_slots[-1], False, {}) else: store.new_peak(peak, None, sub_slots[-1], False, {}) assert store.get_sub_slot( sub_slots[0].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[1].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[2].challenge_chain.get_hash()) is None if peak.overflow: assert store.get_sub_slot( sub_slots[3].challenge_chain.get_hash())[0] == sub_slots[3] else: assert store.get_sub_slot( sub_slots[3].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[4].challenge_chain.get_hash())[0] == sub_slots[4] assert (store.get_finished_sub_slots( peak, blockchain, sub_slots[-1].challenge_chain.get_hash(), False, ) == []) # Test adding non genesis peak directly blocks = bt.get_consecutive_blocks( 2, skip_slots=2, normalized_to_identity=normalized_to_identity) blocks = bt.get_consecutive_blocks( 3, block_list_input=blocks, normalized_to_identity=normalized_to_identity) for block in blocks: await blockchain.receive_block(block) sb = blockchain.block_record(block.header_hash) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain) assert res[0] is None # Add reorg blocks blocks_reorg = bt.get_consecutive_blocks( 20, normalized_to_identity=normalized_to_identity) for block in blocks_reorg: res, _, _ = await blockchain.receive_block(block) if res == ReceiveBlockResult.NEW_PEAK: sb = blockchain.block_record(block.header_hash) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, True, blockchain) assert res[0] is None # Add slots to the end blocks_2 = bt.get_consecutive_blocks( 1, block_list_input=blocks_reorg, skip_slots=2, normalized_to_identity=normalized_to_identity) for slot in blocks_2[-1].finished_sub_slots: store.new_finished_sub_slot(slot, blockchain, blockchain.get_peak()) assert store.get_sub_slot( sub_slots[3].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[4].challenge_chain.get_hash()) is None # Test adding signage point peak = blockchain.get_peak() ss_start_iters = peak.ip_sub_slot_total_iters(test_constants) for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, blockchain, peak, ss_start_iters, uint8(i), [], peak.sub_slot_iters, ) assert store.new_signage_point(i, blockchain, peak, peak.sub_slot_iters, sp) blocks = blocks_reorg while True: blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, normalized_to_identity=normalized_to_identity) res, _, _ = await blockchain.receive_block(blocks[-1]) if res == ReceiveBlockResult.NEW_PEAK: sb = blockchain.block_record(blocks[-1].header_hash) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( blocks[-1].header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, True, blockchain) assert res[0] is None if sb.overflow and sp_sub_slot is not None: assert sp_sub_slot != ip_sub_slot break peak = blockchain.get_peak() assert peak.overflow # Overflow peak should result in 2 finished sub slots assert len(store.finished_sub_slots) == 2 # Add slots to the end, except for the last one, which we will use to test invalid SP blocks_2 = bt.get_consecutive_blocks( 1, block_list_input=blocks, skip_slots=3, normalized_to_identity=normalized_to_identity) for slot in blocks_2[-1].finished_sub_slots[:-1]: store.new_finished_sub_slot(slot, blockchain, blockchain.get_peak()) finished_sub_slots = blocks_2[-1].finished_sub_slots assert len(store.finished_sub_slots) == 4 # Test adding signage points for overflow blocks (sp_sub_slot) ss_start_iters = peak.sp_sub_slot_total_iters(test_constants) # for i in range(peak.signage_point_index, test_constants.NUM_SPS_SUB_SLOT): # if i < peak.signage_point_index: # continue # latest = peak # while latest.total_iters > peak.sp_total_iters(test_constants): # latest = blockchain.blocks[latest.prev_hash] # sp = get_signage_point( # test_constants, # blockchain.blocks, # latest, # ss_start_iters, # uint8(i), # [], # peak.sub_slot_iters, # ) # assert store.new_signage_point(i, blockchain.blocks, peak, peak.sub_slot_iters, sp) # Test adding signage points for overflow blocks (ip_sub_slot) for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, blockchain, peak, peak.ip_sub_slot_total_iters(test_constants), uint8(i), [], peak.sub_slot_iters, ) assert store.new_signage_point(i, blockchain, peak, peak.sub_slot_iters, sp) # Test adding future signage point, a few slots forward (good) saved_sp_hash = None for slot_offset in range(1, len(finished_sub_slots)): for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA, ): sp = get_signage_point( test_constants, blockchain, peak, peak.ip_sub_slot_total_iters(test_constants) + slot_offset * peak.sub_slot_iters, uint8(i), finished_sub_slots[:slot_offset], peak.sub_slot_iters, ) assert sp.cc_vdf is not None saved_sp_hash = sp.cc_vdf.output.get_hash() assert store.new_signage_point(i, blockchain, peak, peak.sub_slot_iters, sp) # Test adding future signage point (bad) for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, blockchain, peak, peak.ip_sub_slot_total_iters(test_constants) + len(finished_sub_slots) * peak.sub_slot_iters, uint8(i), finished_sub_slots[:len(finished_sub_slots)], peak.sub_slot_iters, ) assert not store.new_signage_point(i, blockchain, peak, peak.sub_slot_iters, sp) # Test adding past signage point sp = SignagePoint( blocks[1].reward_chain_block.challenge_chain_sp_vdf, blocks[1].challenge_chain_sp_proof, blocks[1].reward_chain_block.reward_chain_sp_vdf, blocks[1].reward_chain_sp_proof, ) assert not store.new_signage_point( blocks[1].reward_chain_block.signage_point_index, {}, peak, blockchain.block_record( blocks[1].header_hash).sp_sub_slot_total_iters(test_constants), sp, ) # Get signage point by index assert (store.get_signage_point_by_index( finished_sub_slots[0].challenge_chain.get_hash(), 4, finished_sub_slots[0].reward_chain.get_hash(), ) is not None) assert (store.get_signage_point_by_index( finished_sub_slots[0].challenge_chain.get_hash(), 4, std_hash(b"1")) is None) # Get signage point by hash assert store.get_signage_point(saved_sp_hash) is not None assert store.get_signage_point(std_hash(b"2")) is None # Test adding signage points before genesis store.initialize_genesis_sub_slot() assert len(store.finished_sub_slots) == 1 for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, BlockCache({}, {}), None, uint128(0), uint8(i), [], peak.sub_slot_iters, ) assert store.new_signage_point(i, {}, None, peak.sub_slot_iters, sp) blocks_3 = bt.get_consecutive_blocks( 1, skip_slots=2, normalized_to_identity=normalized_to_identity) for slot in blocks_3[-1].finished_sub_slots: store.new_finished_sub_slot(slot, {}, None) assert len(store.finished_sub_slots) == 3 finished_sub_slots = blocks_3[-1].finished_sub_slots for slot_offset in range(1, len(finished_sub_slots) + 1): for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA, ): sp = get_signage_point( test_constants, BlockCache({}, {}), None, slot_offset * peak.sub_slot_iters, uint8(i), finished_sub_slots[:slot_offset], peak.sub_slot_iters, ) assert store.new_signage_point(i, {}, None, peak.sub_slot_iters, sp) # Test adding signage points after genesis blocks_4 = bt.get_consecutive_blocks( 1, normalized_to_identity=normalized_to_identity) blocks_5 = bt.get_consecutive_blocks( 1, block_list_input=blocks_4, skip_slots=1, normalized_to_identity=normalized_to_identity) # If this is not the case, fix test to find a block that is assert (blocks_4[-1].reward_chain_block.signage_point_index < test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA) await blockchain.receive_block(blocks_4[-1]) sb = blockchain.block_record(blocks_4[-1].header_hash) store.new_peak(sb, None, None, False, blockchain) for i in range( sb.signage_point_index + test_constants.NUM_SP_INTERVALS_EXTRA, test_constants.NUM_SPS_SUB_SLOT, ): if is_overflow_block(test_constants, uint8(i)): finished_sub_slots = blocks_5[-1].finished_sub_slots else: finished_sub_slots = [] sp = get_signage_point( test_constants, blockchain, sb, uint128(0), uint8(i), finished_sub_slots, peak.sub_slot_iters, ) assert store.new_signage_point(i, empty_blockchain, sb, peak.sub_slot_iters, sp) # Test future EOS cache store.initialize_genesis_sub_slot() blocks = bt.get_consecutive_blocks( 1, normalized_to_identity=normalized_to_identity) await blockchain.receive_block(blocks[-1]) while True: blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, normalized_to_identity=normalized_to_identity) await blockchain.receive_block(blocks[-1]) sb = blockchain.block_record(blocks[-1].header_hash) if sb.first_in_sub_slot: break assert len(blocks) >= 3 dependant_sub_slots = blocks[-1].finished_sub_slots for block in blocks[:-2]: sb = blockchain.block_record(block.header_hash) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) peak = sb res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain) assert res[0] is None assert store.new_finished_sub_slot(dependant_sub_slots[0], blockchain, peak) is None block = blocks[-2] sb = blockchain.block_record(block.header_hash) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain) assert res[0] == dependant_sub_slots[0] assert res[1] == res[2] == [] # Test future IP cache store.initialize_genesis_sub_slot() blocks = bt.get_consecutive_blocks( 60, normalized_to_identity=normalized_to_identity) for block in blocks[:5]: await blockchain.receive_block(block) sb = blockchain.block_record(block.header_hash) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain) assert res[0] is None case_0, case_1 = False, False for i in range(5, len(blocks) - 1): prev_block = blocks[i] block = blocks[i + 1] new_ip = NewInfusionPointVDF( block.reward_chain_block.get_unfinished().get_hash(), block.reward_chain_block.challenge_chain_ip_vdf, block.challenge_chain_ip_proof, block.reward_chain_block.reward_chain_ip_vdf, block.reward_chain_ip_proof, block.reward_chain_block.infused_challenge_chain_ip_vdf, block.infused_challenge_chain_ip_proof, ) store.add_to_future_ip(new_ip) await blockchain.receive_block(prev_block) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( prev_block.header_hash) sb = blockchain.block_record(prev_block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain) if len(block.finished_sub_slots) == 0: case_0 = True assert res[2] == [new_ip] else: case_1 = True assert res[2] == [] found_ips = [] for ss in block.finished_sub_slots: found_ips += store.new_finished_sub_slot( ss, blockchain, sb) assert found_ips == [new_ip] # If flaky, increase the number of blocks created assert case_0 and case_1
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 if block.height == 0: prev_b: Optional[BlockRecord] = None sub_slot_iters: uint64 = uint64(constants.SUB_SLOT_ITERS_STARTING) else: prev_b = blocks.block_record(block.prev_header_hash) assert prev_b is not None sub_slot_iters = get_next_sub_slot_iters( constants, blocks, prev_b.prev_hash, prev_b.height, prev_b.sub_slot_iters, prev_b.deficit, len(block.finished_sub_slots) > 0, prev_b.sp_total_iters(constants), ) 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), ) prev_transaction_block_hash = ( block.foliage_transaction_block.prev_transaction_block_hash if block.foliage_transaction_block is not None else None) timestamp = block.foliage_transaction_block.timestamp if block.foliage_transaction_block is not None else None fees = block.transactions_info.fees if block.transactions_info is not None else None reward_claims_incorporated = ( block.transactions_info.reward_claims_incorporated if block.transactions_info is not None else None) if len(block.finished_sub_slots) > 0: finished_challenge_slot_hashes: Optional[List[bytes32]] = [ sub_slot.challenge_chain.get_hash() for sub_slot in block.finished_sub_slots ] finished_reward_slot_hashes: Optional[List[bytes32]] = [ sub_slot.reward_chain.get_hash() for sub_slot in block.finished_sub_slots ] finished_infused_challenge_slot_hashes: Optional[List[bytes32]] = [ sub_slot.infused_challenge_chain.get_hash() for sub_slot in block.finished_sub_slots if sub_slot.infused_challenge_chain is not None ] elif block.height == 0: finished_challenge_slot_hashes = [constants.GENESIS_CHALLENGE] finished_reward_slot_hashes = [constants.GENESIS_CHALLENGE] finished_infused_challenge_slot_hashes = None else: finished_challenge_slot_hashes = None finished_reward_slot_hashes = None finished_infused_challenge_slot_hashes = None found_ses_hash: Optional[bytes32] = None ses: Optional[SubEpochSummary] = None if len(block.finished_sub_slots) > 0: for sub_slot in block.finished_sub_slots: if sub_slot.challenge_chain.subepoch_summary_hash is not None: found_ses_hash = sub_slot.challenge_chain.subepoch_summary_hash if found_ses_hash: assert prev_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 cbi = ChallengeBlockInfo( block.reward_chain_block.proof_of_space, block.reward_chain_block.challenge_chain_sp_vdf, block.reward_chain_block.challenge_chain_sp_signature, block.reward_chain_block.challenge_chain_ip_vdf, ) if block.reward_chain_block.infused_challenge_chain_ip_vdf is not None: icc_output: Optional[ ClassgroupElement] = block.reward_chain_block.infused_challenge_chain_ip_vdf.output else: icc_output = None 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 BlockRecord( block.header_hash, block.prev_header_hash, block.height, block.weight, block.total_iters, block.reward_chain_block.signage_point_index, block.reward_chain_block.challenge_chain_ip_vdf.output, icc_output, block.reward_chain_block.get_hash(), cbi.get_hash(), sub_slot_iters, block.foliage.foliage_block_data.pool_target.puzzle_hash, block.foliage.foliage_block_data.farmer_reward_puzzle_hash, required_iters, deficit, overflow, prev_transaction_block_height, timestamp, prev_transaction_block_hash, fees, reward_claims_incorporated, finished_challenge_slot_hashes, finished_infused_challenge_slot_hashes, finished_reward_slot_hashes, ses, )
async def test1(self, two_nodes): num_blocks = 5 test_rpc_port = uint16(21522) nodes, _ = two_nodes full_node_api_1, full_node_api_2 = nodes server_1 = full_node_api_1.full_node.server server_2 = full_node_api_2.full_node.server def stop_node_cb(): full_node_api_1._close() server_1.close_all() full_node_rpc_api = FullNodeRpcApi(full_node_api_1.full_node) config = bt.config hostname = config["self_hostname"] daemon_port = config["daemon_port"] rpc_cleanup = await start_rpc_server( full_node_rpc_api, hostname, daemon_port, test_rpc_port, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) try: client = await FullNodeRpcClient.create(self_hostname, test_rpc_port, bt.root_path, config) state = await client.get_blockchain_state() assert state["peak"] is None assert not state["sync"]["sync_mode"] assert state["difficulty"] > 0 assert state["sub_slot_iters"] > 0 blocks = bt.get_consecutive_blocks(num_blocks) blocks = bt.get_consecutive_blocks( num_blocks, block_list_input=blocks, guarantee_transaction_block=True) assert len(await client.get_unfinished_block_headers()) == 0 assert len((await client.get_block_records(0, 100))) == 0 for block in blocks: if is_overflow_block( test_constants, block.reward_chain_block.signage_point_index): finished_ss = block.finished_sub_slots[:-1] else: finished_ss = block.finished_sub_slots unf = UnfinishedBlock( finished_ss, block.reward_chain_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_block, block.transactions_info, block.transactions_generator, [], ) await full_node_api_1.full_node.respond_unfinished_block( full_node_protocol.RespondUnfinishedBlock(unf), None) await full_node_api_1.full_node.respond_block( full_node_protocol.RespondBlock(block), None) assert len(await client.get_unfinished_block_headers()) > 0 assert len(await client.get_all_block(0, 2)) == 2 state = await client.get_blockchain_state() block = await client.get_block(state["peak"].header_hash) assert block == blocks[-1] assert (await client.get_block(bytes([1] * 32))) is None assert (await client.get_block_record_by_height(2) ).header_hash == blocks[2].header_hash assert len((await client.get_block_records(0, 100))) == num_blocks * 2 assert (await client.get_block_record_by_height(100)) is None ph = list(blocks[-1].get_included_reward_coins())[0].puzzle_hash coins = await client.get_coin_records_by_puzzle_hash(ph) print(coins) assert len(coins) >= 1 additions, removals = await client.get_additions_and_removals( blocks[-1].header_hash) assert len(additions) >= 2 and len(removals) == 0 wallet = WalletTool() wallet_receiver = WalletTool( AugSchemeMPL.key_gen(std_hash(b"123123"))) ph = wallet.get_new_puzzlehash() ph_2 = wallet.get_new_puzzlehash() ph_receiver = wallet_receiver.get_new_puzzlehash() assert len(await client.get_coin_records_by_puzzle_hash(ph)) == 0 assert len( await client.get_coin_records_by_puzzle_hash(ph_receiver)) == 0 blocks = bt.get_consecutive_blocks( 2, block_list_input=blocks, guarantee_transaction_block=True, farmer_reward_puzzle_hash=ph, pool_reward_puzzle_hash=ph, ) for block in blocks[-2:]: await full_node_api_1.full_node.respond_block( full_node_protocol.RespondBlock(block)) assert len(await client.get_coin_records_by_puzzle_hash(ph)) == 2 assert len( await client.get_coin_records_by_puzzle_hash(ph_receiver)) == 0 coin_to_spend = list(blocks[-1].get_included_reward_coins())[0] spend_bundle = wallet.generate_signed_transaction( coin_to_spend.amount, ph_receiver, coin_to_spend) assert len(await client.get_all_mempool_items()) == 0 assert len(await client.get_all_mempool_tx_ids()) == 0 assert (await client.get_mempool_item_by_tx_id(spend_bundle.name() )) is None await client.push_tx(spend_bundle) assert len(await client.get_all_mempool_items()) == 1 assert len(await client.get_all_mempool_tx_ids()) == 1 assert (SpendBundle.from_json_dict( list((await client.get_all_mempool_items() ).values())[0]["spend_bundle"]) == spend_bundle) assert (await client.get_all_mempool_tx_ids())[0] == spend_bundle.name() assert (SpendBundle.from_json_dict( (await client.get_mempool_item_by_tx_id( spend_bundle.name()))["spend_bundle"]) == spend_bundle) await full_node_api_1.farm_new_transaction_block( FarmNewBlockProtocol(ph_2)) assert len( await client.get_coin_records_by_puzzle_hash(ph_receiver)) == 1 assert len( list( filter(lambda cr: not cr.spent, (await client.get_coin_records_by_puzzle_hash(ph))))) == 3 assert len(await client.get_coin_records_by_puzzle_hash(ph, False)) == 3 assert len(await client.get_coin_records_by_puzzle_hash(ph, True)) == 4 assert len(await client.get_coin_records_by_puzzle_hash( ph, True, 0, 100)) == 4 assert len(await client.get_coin_records_by_puzzle_hash( ph, True, 50, 100)) == 0 assert len(await client.get_coin_records_by_puzzle_hash( ph, True, 0, blocks[-1].height + 1)) == 2 assert len(await client.get_coin_records_by_puzzle_hash(ph, True, 0, 1)) == 0 assert len(await client.get_connections()) == 0 await client.open_connection(self_hostname, server_2._port) async def num_connections(): return len(await client.get_connections()) await time_out_assert(10, num_connections, 1) connections = await client.get_connections() await client.close_connection(connections[0]["node_id"]) await time_out_assert(10, num_connections, 0) finally: # Checks that the RPC manages to stop the node client.close() await client.await_closed() await rpc_cleanup()
async def test1(self, two_nodes): num_blocks = 5 test_rpc_port = uint16(21522) full_node_api_1, full_node_api_2, server_1, server_2 = two_nodes def stop_node_cb(): full_node_api_1._close() server_1.close_all() full_node_rpc_api = FullNodeRpcApi(full_node_api_1.full_node) config = bt.config hostname = config["self_hostname"] daemon_port = config["daemon_port"] rpc_cleanup = await start_rpc_server( full_node_rpc_api, hostname, daemon_port, test_rpc_port, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) try: client = await FullNodeRpcClient.create(self_hostname, test_rpc_port, bt.root_path, config) state = await client.get_blockchain_state() assert state["peak"] is None assert not state["sync"]["sync_mode"] assert state["difficulty"] > 0 assert state["sub_slot_iters"] > 0 blocks = bt.get_consecutive_blocks(num_blocks) blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, guarantee_transaction_block=True) assert len(await client.get_unfinished_block_headers()) == 0 assert len((await client.get_block_records(0, 100))) == 0 for block in blocks: if is_overflow_block( test_constants, block.reward_chain_block.signage_point_index): finished_ss = block.finished_sub_slots[:-1] else: finished_ss = block.finished_sub_slots unf = UnfinishedBlock( finished_ss, block.reward_chain_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_block, block.transactions_info, block.transactions_generator, ) await full_node_api_1.full_node.respond_unfinished_block( full_node_protocol.RespondUnfinishedBlock(unf), None) await full_node_api_1.full_node.respond_block( full_node_protocol.RespondBlock(block), None) assert len(await client.get_unfinished_block_headers()) > 0 assert len(await client.get_all_block(0, 2)) == 2 state = await client.get_blockchain_state() block = await client.get_block(state["peak"].header_hash) assert block == blocks[-1] assert (await client.get_block(bytes([1] * 32))) is None assert (await client.get_block_record_by_height(2) ).header_hash == blocks[2].header_hash assert len((await client.get_block_records(0, 100))) == num_blocks + 1 assert (await client.get_block_record_by_height(100)) is None ph = list(blocks[-1].get_included_reward_coins())[0].puzzle_hash coins = await client.get_unspent_coins(ph) assert len(coins) >= 1 additions, removals = await client.get_additions_and_removals( blocks[-1].header_hash) assert len(additions) >= 2 and len(removals) == 0 assert len(await client.get_connections()) == 0 await client.open_connection(self_hostname, server_2._port) async def num_connections(): return len(await client.get_connections()) await time_out_assert(10, num_connections, 1) connections = await client.get_connections() await client.close_connection(connections[0]["node_id"]) await time_out_assert(10, num_connections, 0) finally: # Checks that the RPC manages to stop the node client.close() await client.await_closed() await rpc_cleanup()