async def get_removals_and_additions(self, block: FullBlock) -> Tuple[List[bytes32], List[Coin]]: if block.is_transaction_block(): if block.transactions_generator is not None: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) assert block_generator is not None npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False) removals, additions = block_removals_and_additions(block, npc_result.npc_list) return removals, additions else: return [], list(block.get_included_reward_coins()) else: return [], []
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 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: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) assert block_generator is not None npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False) removals, additions = block_removals_and_additions(block, npc_result.npc_list) else: removals, additions = [], list(block.get_included_reward_coins()) header_block = get_block_header(block, 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: 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() 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 _reconsider_peak( self, block_record: BlockRecord, genesis: bool, fork_point_with_peak: Optional[uint32], npc_result: Optional[NPCResult], ) -> Tuple[Optional[uint32], Optional[uint32], List[BlockRecord]]: """ When a new block is added, this is called, to check if the new block is the new peak of the chain. This also handles reorgs by reverting blocks which are not in the heaviest chain. It returns the height of the fork between the previous chain and the new chain, or returns None if there was no update to the heaviest chain. """ peak = self.get_peak() if genesis: if peak is None: block: Optional[FullBlock] = await self.block_store.get_full_block(block_record.header_hash) assert block is not None # Begins a transaction, because we want to ensure that the coin store and block store are only updated # in sync. if npc_result is not None: removals, additions = block_removals_and_additions(block, npc_result.npc_list) else: removals = [] additions = list(block.get_included_reward_coins()) await self.coin_store.new_block(block, additions, removals) await self.block_store.set_peak(block.header_hash) return uint32(0), uint32(0), [block_record] return None, None, [] assert peak is not None if block_record.weight > peak.weight: # Find the fork. if the block is just being appended, it will return the peak # If no blocks in common, returns -1, and reverts all blocks if fork_point_with_peak is not None: fork_height: int = fork_point_with_peak else: fork_height = find_fork_point_in_chain(self, block_record, peak) # Rollback to fork await self.coin_store.rollback_to_block(fork_height) # Rollback sub_epoch_summaries heights_to_delete = [] for ses_included_height in self.__sub_epoch_summaries.keys(): if ses_included_height > fork_height: heights_to_delete.append(ses_included_height) for height in heights_to_delete: log.info(f"delete ses at height {height}") del self.__sub_epoch_summaries[height] # Collect all blocks from fork point to new peak blocks_to_add: List[Tuple[FullBlock, BlockRecord]] = [] curr = block_record.header_hash while fork_height < 0 or curr != self.height_to_hash(uint32(fork_height)): fetched_full_block: Optional[FullBlock] = await self.block_store.get_full_block(curr) fetched_block_record: Optional[BlockRecord] = await self.block_store.get_block_record(curr) assert fetched_full_block is not None assert fetched_block_record is not None blocks_to_add.append((fetched_full_block, fetched_block_record)) if fetched_full_block.height == 0: # Doing a full reorg, starting at height 0 break curr = fetched_block_record.prev_hash records_to_add = [] for fetched_full_block, fetched_block_record in reversed(blocks_to_add): records_to_add.append(fetched_block_record) if fetched_block_record.is_transaction_block: removals, additions = await self.get_removals_and_additions(fetched_full_block) await self.coin_store.new_block(fetched_full_block, additions, removals) # Changes the peak to be the new peak await self.block_store.set_peak(block_record.header_hash) return uint32(max(fork_height, 0)), block_record.height, records_to_add # This is not a heavier block than the heaviest we have seen, so we don't change the coin set return 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]) additions: List[Coin] = list(block.get_included_reward_coins()) 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, additions = block_removals_and_additions(block, npc_result.npc_list) else: removals, additions = block_removals_and_additions(block, []) 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 block_generator: BlockGenerator = BlockGenerator.from_bytes(prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions(block_generator, constants.MAX_BLOCK_COST_CLVM, True) removals, additions = block_removals_and_additions(block, npc_result.npc_list) header_block = get_block_header(block, 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]