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 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, )
def block_to_sub_block_record( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], 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.sub_block_height == 0: prev_sb: Optional[SubBlockRecord] = None sub_slot_iters: uint64 = uint64(constants.SUB_SLOT_ITERS_STARTING) height = 0 else: prev_sb = sub_blocks[block.prev_header_hash] assert prev_sb is not None if prev_sb.is_block: height = prev_sb.height + 1 else: height = prev_sb.height sub_slot_iters = get_next_sub_slot_iters( constants, sub_blocks, height_to_hash, prev_sb.prev_hash, prev_sb.sub_block_height, prev_sb.sub_slot_iters, prev_sb.deficit, len(block.finished_sub_slots) > 0, prev_sb.sp_total_iters(constants), ) overflow = is_overflow_sub_block( constants, block.reward_chain_sub_block.signage_point_index) deficit = calculate_deficit( constants, block.sub_block_height, prev_sb, overflow, len(block.finished_sub_slots), ) prev_block_hash = block.foliage_block.prev_block_hash if block.foliage_block is not None else None timestamp = block.foliage_block.timestamp if block.foliage_block is not None else None fees = block.transactions_info.fees if block.transactions_info is not None else None # reward_claims_incorporated = ( # block.transactions_info.reward_claims_incorporated if block.transactions_info is not None else None # ) if len(block.finished_sub_slots) > 0: finished_challenge_slot_hashes: Optional[List[bytes32]] = [ sub_slot.challenge_chain.get_hash() for sub_slot in block.finished_sub_slots ] finished_reward_slot_hashes: Optional[List[bytes32]] = [ sub_slot.reward_chain.get_hash() for sub_slot in block.finished_sub_slots ] finished_infused_challenge_slot_hashes: Optional[List[bytes32]] = [ sub_slot.infused_challenge_chain.get_hash() for sub_slot in block.finished_sub_slots if sub_slot.infused_challenge_chain is not None ] elif block.sub_block_height == 0: finished_challenge_slot_hashes = [constants.FIRST_CC_CHALLENGE] finished_reward_slot_hashes = [constants.FIRST_RC_CHALLENGE] finished_infused_challenge_slot_hashes = None else: finished_challenge_slot_hashes = None finished_reward_slot_hashes = None finished_infused_challenge_slot_hashes = None found_ses_hash: Optional[bytes32] = None ses: Optional[SubEpochSummary] = None if len(block.finished_sub_slots) > 0: for sub_slot in block.finished_sub_slots: if sub_slot.challenge_chain.subepoch_summary_hash is not None: found_ses_hash = sub_slot.challenge_chain.subepoch_summary_hash if found_ses_hash: assert prev_sb is not None assert len(block.finished_sub_slots) > 0 ses = make_sub_epoch_summary( constants, sub_blocks, block.sub_block_height, sub_blocks[prev_sb.prev_hash], block.finished_sub_slots[0].challenge_chain.new_difficulty, block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters, ) assert ses.get_hash() == found_ses_hash cbi = ChallengeBlockInfo( block.reward_chain_sub_block.proof_of_space, block.reward_chain_sub_block.challenge_chain_sp_vdf, block.reward_chain_sub_block.challenge_chain_sp_signature, block.reward_chain_sub_block.challenge_chain_ip_vdf, ) if block.reward_chain_sub_block.infused_challenge_chain_ip_vdf is not None: icc_output: Optional[ ClassgroupElement] = block.reward_chain_sub_block.infused_challenge_chain_ip_vdf.output else: icc_output = None return SubBlockRecord( block.header_hash, block.prev_header_hash, block.sub_block_height, uint32(height), block.weight, block.total_iters, block.reward_chain_sub_block.signage_point_index, block.reward_chain_sub_block.challenge_chain_ip_vdf.output, icc_output, block.reward_chain_sub_block.get_hash(), cbi.get_hash(), sub_slot_iters, block.foliage_sub_block.foliage_sub_block_data.pool_target.puzzle_hash, block.foliage_sub_block.foliage_sub_block_data. farmer_reward_puzzle_hash, required_iters, deficit, overflow, timestamp, prev_block_hash, fees, # reward_claims_incorporated, finished_challenge_slot_hashes, finished_infused_challenge_slot_hashes, finished_reward_slot_hashes, ses, )
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, )
def finish_sub_block( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], finished_sub_slots: List[EndOfSubSlotBundle], sub_slot_start_total_iters: uint128, signage_point_index: uint8, unfinished_block: UnfinishedBlock, required_iters: uint64, ip_iters: uint64, slot_cc_challenge: bytes32, slot_rc_challenge: bytes32, latest_sub_block: SubBlockRecord, sub_slot_iters: uint64, difficulty: uint64, ): is_overflow = is_overflow_sub_block(constants, signage_point_index) cc_vdf_challenge = slot_cc_challenge if len(finished_sub_slots) == 0: new_ip_iters = unfinished_block.total_iters - latest_sub_block.total_iters cc_vdf_input = latest_sub_block.challenge_vdf_output rc_vdf_challenge = latest_sub_block.reward_infusion_new_challenge else: new_ip_iters = ip_iters cc_vdf_input = ClassgroupElement.get_default_element() rc_vdf_challenge = slot_rc_challenge cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof( constants, cc_vdf_input, cc_vdf_challenge, new_ip_iters, ) cc_ip_vdf = replace(cc_ip_vdf, number_of_iterations=ip_iters) deficit = calculate_deficit( constants, uint32(latest_sub_block.sub_block_height + 1), latest_sub_block, is_overflow, len(finished_sub_slots), ) icc_ip_vdf, icc_ip_proof = get_icc( constants, unfinished_block.total_iters, finished_sub_slots, latest_sub_block, sub_blocks, uint128(sub_slot_start_total_iters + sub_slot_iters) if is_overflow else sub_slot_start_total_iters, deficit, ) rc_ip_vdf, rc_ip_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), rc_vdf_challenge, new_ip_iters, ) assert unfinished_block is not None sp_total_iters = uint128( sub_slot_start_total_iters + calculate_sp_iters(constants, sub_slot_iters, signage_point_index) ) full_block: FullBlock = unfinished_block_to_full_block( unfinished_block, cc_ip_vdf, cc_ip_proof, rc_ip_vdf, rc_ip_proof, icc_ip_vdf, icc_ip_proof, finished_sub_slots, latest_sub_block, sub_blocks, sp_total_iters, difficulty, ) sub_block_record = block_to_sub_block_record( constants, sub_blocks, height_to_hash, required_iters, full_block, None ) return full_block, sub_block_record