def get_next_difficulty(self, header_hash: bytes32, new_slot: bool) -> uint64: assert self.contains_block(header_hash) curr = self.block_record(header_hash) if curr.height <= 2: return self.constants.DIFFICULTY_STARTING return get_next_sub_slot_iters_and_difficulty(self.constants, new_slot, curr, self)[1]
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 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 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 ) npc_result = None if block.transactions_generator is not None: assert block.transactions_info is not None try: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) except ValueError: return PreValidationResult(uint16(Err.GENERATOR_REF_HAS_NO_GENERATOR.value), None, None) if block_generator is None: return PreValidationResult(uint16(Err.GENERATOR_REF_HAS_NO_GENERATOR.value), None, None) npc_result = get_name_puzzle_conditions( block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False ) 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), npc_result, None, self.get_block_generator, ) if error_code is not None: return PreValidationResult(uint16(error_code.value), None, None) return PreValidationResult(None, required_iters, cost_result)
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
async def pre_validate_blocks_multiprocessing( constants: ConsensusConstants, constants_json: Dict, block_records: BlockchainInterface, blocks: Sequence[Union[FullBlock, HeaderBlock]], pool: ProcessPoolExecutor, check_filter: bool, npc_results: Dict[uint32, NPCResult], get_block_generator: Optional[Callable], batch_size: int, wp_summaries: Optional[List[SubEpochSummary]] = None, ) -> 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: constants_json: pool: constants: block_records: blocks: list of full blocks to validate (must be connected to current chain) npc_results get_block_generator """ 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: assert block_records.contains_block(block.prev_header_hash) if 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) 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, ) if block_rec.sub_epoch_summary_included is not None and wp_summaries is not None: idx = int(block.height / constants.SUB_EPOCH_BLOCKS) - 1 next_ses = wp_summaries[idx] if not block_rec.sub_epoch_summary_included.get_hash( ) == next_ses.get_hash(): log.error( "sub_epoch_summary does not match wp sub_epoch_summary list" ) return None # Makes sure to not override the valid blocks already in block_records if not block_records.contains_block(block_rec.header_hash): block_records.add_block_record( block_rec) # Temporarily add block to dict recent_blocks[block_rec.header_hash] = block_rec recent_blocks_compressed[block_rec.header_hash] = block_rec else: recent_blocks[block_rec.header_hash] = block_records.block_record( block_rec.header_hash) recent_blocks_compressed[ block_rec.header_hash] = block_records.block_record( block_rec.header_hash) prev_b = block_rec diff_ssis.append((difficulty, sub_slot_iters)) block_dict: Dict[bytes32, Union[FullBlock, HeaderBlock]] = {} for i, block in enumerate(blocks): block_dict[block.header_hash] = block 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() } npc_results_pickled = {} for k, v in npc_results.items(): npc_results_pickled[k] = bytes(v) 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 b_pickled: Optional[List[bytes]] = None hb_pickled: Optional[List[bytes]] = None previous_generators: List[Optional[bytes]] = [] for block in blocks_to_validate: # We ONLY add blocks which are in the past, based on header hashes (which are validated later) to the # prev blocks dict. This is important since these blocks are assumed to be valid and are used as previous # generator references prev_blocks_dict: Dict[uint32, Union[FullBlock, HeaderBlock]] = {} curr_b: Union[FullBlock, HeaderBlock] = block while curr_b.prev_header_hash in block_dict: curr_b = block_dict[curr_b.prev_header_hash] prev_blocks_dict[curr_b.header_hash] = curr_b if isinstance(block, FullBlock): assert get_block_generator is not None if b_pickled is None: b_pickled = [] b_pickled.append(bytes(block)) try: block_generator: Optional[ BlockGenerator] = await get_block_generator( block, prev_blocks_dict) except ValueError: return None if block_generator is not None: previous_generators.append(bytes(block_generator)) else: previous_generators.append(None) else: if hb_pickled is None: hb_pickled = [] hb_pickled.append(bytes(block)) futures.append(asyncio.get_running_loop().run_in_executor( pool, batch_pre_validate_blocks, constants_json, final_pickled, b_pickled, hb_pickled, previous_generators, npc_results_pickled, check_filter, [diff_ssis[j][0] for j in range(i, end_i)], [diff_ssis[j][1] for j in range(i, end_i)], )) # 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 ]
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
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, )
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 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_and_difficulty( constants, len(block.finished_sub_slots) > 0, prev_b, blocks)[0] overflow = is_overflow_block(constants, signage_point_index) if (len(block.finished_sub_slots) > 0 and block.finished_sub_slots[0].challenge_chain.subepoch_summary_hash is not None): return None if can_finish_soon: deficit: uint8 = uint8( 0) # Assume that our deficit will go to zero soon can_finish_se = True if height_can_be_first_in_epoch(constants, uint32(prev_b.height + 2)): can_finish_epoch = True if (prev_b.height + 2) % constants.SUB_EPOCH_BLOCKS > 1: curr: BlockRecord = prev_b while curr.height % constants.SUB_EPOCH_BLOCKS > 0: if (curr.sub_epoch_summary_included is not None and curr.sub_epoch_summary_included.new_difficulty is not None): can_finish_epoch = False curr = blocks.block_record(curr.prev_hash) if (curr.sub_epoch_summary_included is not None and curr.sub_epoch_summary_included.new_difficulty is not None): can_finish_epoch = False elif height_can_be_first_in_epoch( constants, uint32(prev_b.height + constants.MAX_SUB_SLOT_BLOCKS + 2)): can_finish_epoch = True else: can_finish_epoch = False else: 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, blocks, uint32(prev_b.height + 1), prev_b.header_hash if prev_b is not None else None, deficit, False, ) # 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, False, # Already checked above 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, False, # Already checked above 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, )