async def get_unfinished_block_headers( self) -> List[UnfinishedHeaderBlock]: response = await self.fetch("get_unfinished_block_headers", {}) return [ UnfinishedHeaderBlock.from_json_dict(r) for r in response["headers"] ]
async def validate_unfinished_block( self, block: UnfinishedBlock, skip_overflow_ss_validation=True) -> PreValidationResult: if (not self.contains_block(block.prev_header_hash) and not block.prev_header_hash == self.constants.GENESIS_CHALLENGE): return PreValidationResult( uint16(Err.INVALID_PREV_BLOCK_HASH.value), None, None) unfinished_header_block = UnfinishedHeaderBlock( block.finished_sub_slots, block.reward_chain_block, block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_block, b"", ) prev_b = self.try_block_record( unfinished_header_block.prev_header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( self.constants, len(unfinished_header_block.finished_sub_slots) > 0, prev_b, self) required_iters, error = validate_unfinished_header_block( self.constants, self, unfinished_header_block, False, difficulty, sub_slot_iters, skip_overflow_ss_validation, ) if error is not None: return PreValidationResult(uint16(error.code.value), None, None) prev_height = (-1 if block.prev_header_hash == self.constants.GENESIS_CHALLENGE else self.block_record(block.prev_header_hash).height) error_code, cost_result = await validate_block_body( self.constants, self, self.block_store, self.coin_store, self.get_peak(), block, uint32(prev_height + 1), None, ) if error_code is not None: return PreValidationResult(uint16(error_code.value), None, None) return PreValidationResult(None, required_iters, cost_result)
async def validate_unfinished_block( self, block: UnfinishedBlock, skip_overflow_ss_validation=True ) -> Tuple[Optional[uint64], Optional[Err]]: if ( not self.contains_sub_block(block.prev_header_hash) and not block.prev_header_hash == self.constants.GENESIS_CHALLENGE ): return None, Err.INVALID_PREV_BLOCK_HASH unfinished_header_block = UnfinishedHeaderBlock( block.finished_sub_slots, block.reward_chain_sub_block, block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage_sub_block, block.foliage_block, b"", ) prev_sb = self.try_sub_block(unfinished_header_block.prev_header_hash) sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty( self.constants, unfinished_header_block, prev_sb, self ) required_iters, error = validate_unfinished_header_block( self.constants, self, unfinished_header_block, False, difficulty, sub_slot_iters, skip_overflow_ss_validation, ) if error is not None: return None, error.code prev_height = ( -1 if block.prev_header_hash == self.constants.GENESIS_CHALLENGE else self.sub_block_record(block.prev_header_hash).height ) error_code = await validate_block_body( self.constants, self, self.block_store, self.coin_store, self.get_peak(), block, uint32(prev_height + 1), None, ) if error_code is not None: return None, error_code return required_iters, None
async def get_unfinished_block_headers(self, request: Dict) -> Optional[Dict]: peak: Optional[BlockRecord] = self.service.blockchain.get_peak() if peak is None: return {"headers": []} response_headers: List[UnfinishedHeaderBlock] = [] for ub_height, block, _ in ( self.service.full_node_store.get_unfinished_blocks()).values(): if ub_height == peak.height: unfinished_header_block = UnfinishedHeaderBlock( block.finished_sub_slots, block.reward_chain_block, block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_block, b"", ) response_headers.append(unfinished_header_block) return {"headers": response_headers}
async def receive_block( self, block_record: HeaderBlockRecord, pre_validation_result: Optional[PreValidationResult] = None, trusted: bool = False, fork_point_with_peak: Optional[uint32] = None, ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32]]: """ Adds a new block into the blockchain, if it's valid and connected to the current blockchain, regardless of whether it is the child of a head, or another block. Returns a header if block is added to head. Returns an error if the block is invalid. Also returns the fork height, in the case of a new peak. """ block = block_record.header genesis: bool = block.height == 0 if self.contains_sub_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None if not self.contains_sub_block(block.prev_header_hash) and not genesis: return ( ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ) if block.height == 0: prev_sb: Optional[SubBlockRecord] = None else: prev_sb = self.sub_block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty( self.constants, block, prev_sb, self) if trusted is False and pre_validation_result is None: required_iters, error = validate_finished_header_block( self.constants, self, block, False, difficulty, sub_slot_iters) elif trusted: unfinished_header_block = UnfinishedHeaderBlock( block.finished_sub_slots, block.reward_chain_sub_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage_sub_block, block.foliage_block, block.transactions_filter, ) required_iters, val_error = validate_unfinished_header_block( self.constants, self, unfinished_header_block, False, difficulty, sub_slot_iters, False, True) error = ValidationError( Err(val_error)) if val_error is not None else None else: assert pre_validation_result is not None required_iters = pre_validation_result.required_iters error = (ValidationError(Err(pre_validation_result.error)) if pre_validation_result.error is not None else None) if error is not None: return ReceiveBlockResult.INVALID_BLOCK, error.code, None assert required_iters is not None sub_block = block_to_sub_block_record( self.constants, self, required_iters, None, block, ) # Always add the block to the database await self.block_store.add_block_record(block_record, sub_block) self.add_sub_block(sub_block) self.clean_sub_block_record(sub_block.height - self.constants.SUB_BLOCKS_CACHE_SIZE) fork_height: Optional[uint32] = await self._reconsider_peak( sub_block, genesis, fork_point_with_peak) if fork_height is not None: self.log.info( f"💰 Updated wallet peak to sub height {sub_block.height}, weight {sub_block.weight}, " ) return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
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
async def validate_unfinished_block( self, block: UnfinishedBlock, skip_overflow_ss_validation=True ) -> Tuple[Optional[uint64], Optional[Err]]: if (block.prev_header_hash not in self.sub_blocks and not block.prev_header_hash == self.constants.GENESIS_PREV_HASH): return None, Err.INVALID_PREV_BLOCK_HASH unfinished_header_block = UnfinishedHeaderBlock( block.finished_sub_slots, block.reward_chain_sub_block, block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage_sub_block, block.foliage_block, b"", ) prev_sb = self.sub_blocks.get(unfinished_header_block.prev_header_hash, None) sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty( self.constants, unfinished_header_block, self.sub_height_to_hash, prev_sb, self.sub_blocks) required_iters, error = validate_unfinished_header_block( self.constants, self.sub_blocks, unfinished_header_block, False, difficulty, sub_slot_iters, skip_overflow_ss_validation, ) if error is not None: return None, error.code prev_sub_height = ( -1 if block.prev_header_hash == self.constants.GENESIS_PREV_HASH else self.sub_blocks[block.prev_header_hash].sub_block_height) if block.is_block(): assert block.foliage_block is not None height: Optional[uint32] = block.foliage_block.height else: height = None error_code = await validate_block_body( self.constants, self.sub_blocks, self.sub_height_to_hash, self.block_store, self.coin_store, self.get_peak(), block, uint32(prev_sub_height + 1), height, ) if error_code is not None: return None, error_code return required_iters, None