def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], header_blocks_pickled: List[bytes], transaction_generators: List[Optional[bytes]], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], validate_transactions: bool, ) -> List[bytes]: assert len(header_blocks_pickled) == len(transaction_generators) blocks = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) for i in range(len(header_blocks_pickled)): try: header_block: HeaderBlock = HeaderBlock.from_bytes( header_blocks_pickled[i]) generator: Optional[bytes] = transaction_generators[i] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) cost_result: Optional[CostResult] = None error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) if constants_dict["NETWORK_TYPE"] == NetworkType.MAINNET.value: cost_result = None else: if not error and generator is not None and validate_transactions: cost_result = calculate_cost_of_program( SerializedProgram.from_bytes(generator), constants.CLVM_COST_RATIO_CONSTANT) results.append( PreValidationResult(error_int, required_iters, cost_result)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) return [bytes(r) for r in results]
async def receive_block( self, header_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 = header_block_record.header genesis: bool = block.height == 0 if self.contains_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None if not self.contains_block(block.prev_header_hash) and not genesis: return ( ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ) if block.height == 0: prev_b: Optional[BlockRecord] = None else: prev_b = self.block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( self.constants, len(block.finished_sub_slots) > 0, prev_b, 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_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_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 block_record = block_to_block_record( self.constants, self, required_iters, None, block, ) # Always add the block to the database async with self.block_store.db_wrapper.lock: try: await self.block_store.db_wrapper.begin_transaction() await self.block_store.add_block_record( header_block_record, block_record) self.add_block_record(block_record) self.clean_block_record(block_record.height - self.constants.BLOCKS_CACHE_SIZE) fork_height: Optional[uint32] = await self._reconsider_peak( block_record, genesis, fork_point_with_peak) await self.block_store.db_wrapper.commit_transaction() except BaseException as e: self.log.error(f"Error during db transaction: {e}") await self.block_store.db_wrapper.rollback_transaction() raise if fork_height is not None: self.log.info( f"💰 Updated wallet peak to height {block_record.height}, weight {block_record.weight}, " ) return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
async def receive_block( self, block: HeaderBlock) -> Tuple[ReceiveBlockResult, Optional[Err]]: if self.contains_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None if not self.contains_block( block.prev_header_hash) and block.height > 0: return ReceiveBlockResult.DISCONNECTED_BLOCK, None if (len(block.finished_sub_slots) > 0 and block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters is not None): assert block.finished_sub_slots[ 0].challenge_chain.new_difficulty is not None # They both change together sub_slot_iters: uint64 = block.finished_sub_slots[ 0].challenge_chain.new_sub_slot_iters difficulty: uint64 = block.finished_sub_slots[ 0].challenge_chain.new_difficulty else: sub_slot_iters = self._sub_slot_iters difficulty = self._difficulty required_iters, error = validate_finished_header_block( self.constants, self, block, False, difficulty, sub_slot_iters, False) if error is not None: return ReceiveBlockResult.INVALID_BLOCK, error.code if required_iters is None: return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_POSPACE block_record: BlockRecord = block_to_block_record( self.constants, self, required_iters, None, block, sub_slot_iters) self.add_block_record(block_record) if self._peak is None: if block_record.is_transaction_block: latest_timestamp = block_record.timestamp else: latest_timestamp = None self._height_to_hash[ block_record.height] = block_record.header_hash await self.set_peak_block(block, latest_timestamp) return ReceiveBlockResult.NEW_PEAK, None elif block_record.weight > self._peak.weight: if block_record.prev_hash == self._peak.header_hash: fork_height: int = self._peak.height else: fork_height = find_fork_point_in_chain(self, block_record, self._peak) await self._rollback_to_height(fork_height) curr_record: BlockRecord = block_record latest_timestamp = self._latest_timestamp while curr_record.height > fork_height: self._height_to_hash[ curr_record.height] = curr_record.header_hash if curr_record.timestamp is not None and curr_record.timestamp > latest_timestamp: latest_timestamp = curr_record.timestamp if curr_record.height == 0: break curr_record = self.block_record(curr_record.prev_hash) self._sub_slot_iters = block_record.sub_slot_iters self._difficulty = uint64( block_record.weight - self.block_record(block_record.prev_hash).weight) await self.set_peak_block(block, latest_timestamp) self.clean_block_records() return ReceiveBlockResult.NEW_PEAK, None return ReceiveBlockResult.ADDED_AS_ORPHAN, None
async def receive_block( self, block: FullBlock, pre_validation_result: Optional[PreValidationResult] = None, fork_point_with_peak: Optional[uint32] = None, summaries_to_check: List[SubEpochSummary] = None, # passed only on long sync ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32]]: """ This method must be called under the blockchain lock 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. """ genesis: bool = block.height == 0 if self.contains_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None if not self.contains_block(block.prev_header_hash) and not genesis: return ( ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ) if not genesis and (self.block_record(block.prev_header_hash).height + 1) != block.height: return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_HEIGHT, None npc_result: Optional[NPCResult] = None if pre_validation_result is None: if block.height == 0: prev_b: Optional[BlockRecord] = None else: prev_b = self.block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( self.constants, len(block.finished_sub_slots) > 0, prev_b, self ) if block.is_transaction_block(): if block.transactions_generator is not None: try: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) except ValueError: return ReceiveBlockResult.INVALID_BLOCK, Err.GENERATOR_REF_HAS_NO_GENERATOR, None assert block_generator is not None and block.transactions_info is not None npc_result = get_name_puzzle_conditions( block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False ) removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) else: removals, tx_additions = [], [] header_block = get_block_header(block, tx_additions, removals) else: npc_result = None header_block = get_block_header(block, [], []) required_iters, error = validate_finished_header_block( self.constants, self, header_block, False, difficulty, sub_slot_iters, ) if error is not None: return ReceiveBlockResult.INVALID_BLOCK, error.code, None else: npc_result = pre_validation_result.npc_result required_iters = pre_validation_result.required_iters assert pre_validation_result.error is None assert required_iters is not None error_code, _ = await validate_block_body( self.constants, self, self.block_store, self.coin_store, self.get_peak(), block, block.height, npc_result, fork_point_with_peak, self.get_block_generator, ) if error_code is not None: return ReceiveBlockResult.INVALID_BLOCK, error_code, None block_record = block_to_block_record( self.constants, self, required_iters, block, None, ) # Always add the block to the database async with self.block_store.db_wrapper.lock: try: # Perform the DB operations to update the state, and rollback if something goes wrong await self.block_store.db_wrapper.begin_transaction() await self.block_store.add_full_block(block, block_record) fork_height, peak_height, records = await self._reconsider_peak( block_record, genesis, fork_point_with_peak, npc_result ) await self.block_store.db_wrapper.commit_transaction() # Then update the memory cache. It is important that this task is not cancelled and does not throw self.add_block_record(block_record) for fetched_block_record in records: self.__height_to_hash[fetched_block_record.height] = fetched_block_record.header_hash if fetched_block_record.sub_epoch_summary_included is not None: self.__sub_epoch_summaries[ fetched_block_record.height ] = fetched_block_record.sub_epoch_summary_included if peak_height is not None: self._peak_height = peak_height self.block_store.cache_block(block) except BaseException: await self.block_store.db_wrapper.rollback_transaction() raise if fork_height is not None: return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], full_blocks_pickled: Optional[List[bytes]], header_blocks_pickled: Optional[List[bytes]], prev_transaction_generators: List[Optional[bytes]], npc_results: Dict[uint32, bytes], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], ) -> List[bytes]: blocks = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) if full_blocks_pickled is not None and header_blocks_pickled is not None: assert ValueError("Only one should be passed here") if full_blocks_pickled is not None: for i in range(len(full_blocks_pickled)): try: block: FullBlock = FullBlock.from_bytes(full_blocks_pickled[i]) tx_additions: List[Coin] = [] removals: List[bytes32] = [] npc_result: Optional[NPCResult] = None if block.height in npc_results: npc_result = NPCResult.from_bytes( npc_results[block.height]) assert npc_result is not None if npc_result.npc_list is not None: removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: removals, tx_additions = [], [] if block.transactions_generator is not None and npc_result is None: prev_generator_bytes = prev_transaction_generators[i] assert prev_generator_bytes is not None assert block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), True) removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) header_block = get_block_header(block, tx_additions, removals) required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, npc_result)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) elif header_blocks_pickled is not None: for i in range(len(header_blocks_pickled)): try: header_block = HeaderBlock.from_bytes(header_blocks_pickled[i]) required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, None)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) return [bytes(r) for r in results]
async def receive_block( self, block: FullBlock, pre_validation_result: Optional[PreValidationResult] = None, fork_point_with_peak: Optional[uint32] = None, ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32]]: """ This method must be called under the blockchain lock 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. """ genesis: bool = block.height == 0 if self.contains_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None if not self.contains_block(block.prev_header_hash) and not genesis: return ( ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ) if not genesis and (self.block_record(block.prev_header_hash).height + 1) != block.height: return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_HEIGHT, None if pre_validation_result is None: if block.height == 0: prev_b: Optional[BlockRecord] = None else: prev_b = self.block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( self.constants, len(block.finished_sub_slots) > 0, prev_b, self ) required_iters, error = validate_finished_header_block( self.constants, self, block.get_block_header(), False, difficulty, sub_slot_iters, ) if error is not None: return ReceiveBlockResult.INVALID_BLOCK, error.code, None else: required_iters = pre_validation_result.required_iters assert pre_validation_result.error is None assert required_iters is not None error_code, _ = await validate_block_body( self.constants, self, self.block_store, self.coin_store, self.get_peak(), block, block.height, pre_validation_result.cost_result if pre_validation_result is not None else None, fork_point_with_peak, ) if error_code is not None: return ReceiveBlockResult.INVALID_BLOCK, error_code, None block_record = block_to_block_record( self.constants, self, required_iters, block, None, ) # Always add the block to the database async with self.block_store.db_wrapper.lock: try: await self.block_store.db_wrapper.begin_transaction() await self.block_store.add_full_block(block, block_record) fork_height, peak_height, records = await self._reconsider_peak( block_record, genesis, fork_point_with_peak ) await self.block_store.db_wrapper.commit_transaction() self.add_block_record(block_record) for fetched_block_record in records: self.__height_to_hash[fetched_block_record.height] = fetched_block_record.header_hash if fetched_block_record.sub_epoch_summary_included is not None: self.__sub_epoch_summaries[ fetched_block_record.height ] = fetched_block_record.sub_epoch_summary_included if peak_height is not None: self._peak_height = peak_height except BaseException: await self.block_store.db_wrapper.rollback_transaction() raise if fork_height is not None: return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
async def receive_block( self, header_block_record: HeaderBlockRecord, pre_validation_result: Optional[PreValidationResult] = None, trusted: bool = False, fork_point_with_peak: Optional[uint32] = None, additional_coin_spends: List[CoinSpend] = 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. """ if additional_coin_spends is None: additional_coin_spends = [] block = header_block_record.header genesis: bool = block.height == 0 if self.contains_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None if not self.contains_block(block.prev_header_hash) and not genesis: return ( ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ) if block.height == 0: prev_b: Optional[BlockRecord] = None else: prev_b = self.block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( self.constants, len(block.finished_sub_slots) > 0, prev_b, 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_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage, block.foliage_transaction_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 = 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 block_record = block_to_block_record( self.constants, self, required_iters, None, block, ) heights_changed: Set[Tuple[uint32, Optional[bytes32]]] = set() # Always add the block to the database async with self.wallet_state_manager_lock: async with self.block_store.db_wrapper.lock: try: await self.block_store.db_wrapper.begin_transaction() await self.block_store.add_block_record(header_block_record, block_record, additional_coin_spends) self.add_block_record(block_record) self.clean_block_record(block_record.height - self.constants.BLOCKS_CACHE_SIZE) fork_height, records_to_add = await self._reconsider_peak( block_record, genesis, fork_point_with_peak, additional_coin_spends, heights_changed ) for record in records_to_add: if record.sub_epoch_summary_included is not None: self.__sub_epoch_summaries[record.height] = record.sub_epoch_summary_included await self.block_store.db_wrapper.commit_transaction() except BaseException as e: self.log.error(f"Error during db transaction: {e}") if self.block_store.db_wrapper.db._connection is not None: await self.block_store.db_wrapper.rollback_transaction() self.remove_block_record(block_record.header_hash) self.block_store.rollback_cache_block(block_record.header_hash) await self.coin_store.rebuild_wallet_cache() await self.tx_store.rebuild_tx_cache() await self.pool_store.rebuild_cache() for height, replaced in heights_changed: # If it was replaced change back to the previous value otherwise pop the change if replaced is not None: self.__height_to_hash[height] = replaced else: self.__height_to_hash.pop(height) raise if fork_height is not None: self.log.info(f"💰 Updated wallet peak to height {block_record.height}, weight {block_record.weight}, ") return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], full_blocks_pickled: Optional[List[bytes]], header_blocks_pickled: Optional[List[bytes]], prev_transaction_generators: List[Optional[bytes]], npc_results: Dict[uint32, bytes], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], validate_signatures: bool, ) -> List[bytes]: blocks: Dict[bytes, BlockRecord] = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) if full_blocks_pickled is not None and header_blocks_pickled is not None: assert ValueError("Only one should be passed here") # In this case, we are validating full blocks, not headers if full_blocks_pickled is not None: for i in range(len(full_blocks_pickled)): try: block: FullBlock = FullBlock.from_bytes(full_blocks_pickled[i]) tx_additions: List[Coin] = [] removals: List[bytes32] = [] npc_result: Optional[NPCResult] = None if block.height in npc_results: npc_result = NPCResult.from_bytes( npc_results[block.height]) assert npc_result is not None if npc_result.npc_list is not None: removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: removals, tx_additions = [], [] if block.transactions_generator is not None and npc_result is None: prev_generator_bytes = prev_transaction_generators[i] assert prev_generator_bytes is not None assert block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, height=block.height, ) removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) if npc_result is not None and npc_result.error is not None: results.append( PreValidationResult(uint16(npc_result.error), None, npc_result, False)) continue header_block = get_block_header(block, tx_additions, removals) # TODO: address hint error and remove ignore # error: Argument 1 to "BlockCache" has incompatible type "Dict[bytes, BlockRecord]"; expected # "Dict[bytes32, BlockRecord]" [arg-type] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), # type: ignore[arg-type] header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) successfully_validated_signatures = False # If we failed CLVM, no need to validate signature, the block is already invalid if error_int is None: # If this is False, it means either we don't have a signature (not a tx block) or we have an invalid # signature (which also puts in an error) or we didn't validate the signature because we want to # validate it later. receive_block will attempt to validate the signature later. if validate_signatures: if npc_result is not None and block.transactions_info is not None: pairs_pks, pairs_msgs = pkm_pairs( npc_result.npc_list, constants.AGG_SIG_ME_ADDITIONAL_DATA) pks_objects: List[G1Element] = [ G1Element.from_bytes(pk) for pk in pairs_pks ] if not AugSchemeMPL.aggregate_verify( pks_objects, pairs_msgs, block. transactions_info.aggregated_signature): error_int = uint16( Err.BAD_AGGREGATE_SIGNATURE.value) else: successfully_validated_signatures = True results.append( PreValidationResult(error_int, required_iters, npc_result, successfully_validated_signatures)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None, False)) # In this case, we are validating header blocks elif header_blocks_pickled is not None: for i in range(len(header_blocks_pickled)): try: header_block = HeaderBlock.from_bytes(header_blocks_pickled[i]) # TODO: address hint error and remove ignore # error: Argument 1 to "BlockCache" has incompatible type "Dict[bytes, BlockRecord]"; expected # "Dict[bytes32, BlockRecord]" [arg-type] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), # type: ignore[arg-type] header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, None, False)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None, False)) return [bytes(r) for r in results]