async def _send_tips_to_farmers( self, delivery: Delivery = Delivery.BROADCAST ) -> OutboundMessageGenerator: """ Sends all of the current heads to all farmer peers. Also sends the latest estimated proof of time rate, so farmer can calulate which proofs are good. """ requests: List[farmer_protocol.ProofOfSpaceFinalized] = [] async with self.store.lock: tips = self.blockchain.get_current_tips() for tip in tips: assert tip.proof_of_time and tip.challenge challenge_hash = tip.challenge.get_hash() height = tip.challenge.height quality = tip.proof_of_space.verify_and_get_quality() if tip.height > 0: difficulty: uint64 = self.blockchain.get_next_difficulty( tip.prev_header_hash) else: difficulty = tip.weight requests.append( farmer_protocol.ProofOfSpaceFinalized( challenge_hash, height, tip.weight, quality, difficulty)) proof_of_time_rate: uint64 = self.blockchain.get_next_ips( tips[0].header_hash) rate_update = farmer_protocol.ProofOfTimeRate(proof_of_time_rate) yield OutboundMessage(NodeType.FARMER, Message("proof_of_time_rate", rate_update), delivery) for request in requests: yield OutboundMessage(NodeType.FARMER, Message("proof_of_space_finalized", request), delivery)
async def block(self, block: peer_protocol.Block) -> OutboundMessageGenerator: """ Receive a full block from a peer full node (or ourselves). """ header_hash = block.block.header_block.header.get_hash() # Adds the block to seen, and check if it's seen before if self.store.seen_block(header_hash): return async with self.store.lock: if await self.store.get_sync_mode(): # Add the block to our potential tips list await self.store.add_potential_tip(block.block) return # Tries to add the block to the blockchain added: ReceiveBlockResult = await self.blockchain.receive_block( block.block) # Always immediately add the block to the database, after updating blockchain state if (added == ReceiveBlockResult.ADDED_AS_ORPHAN or added == ReceiveBlockResult.ADDED_TO_HEAD): await self.store.add_block(block.block) if added == ReceiveBlockResult.ALREADY_HAVE_BLOCK: return elif added == ReceiveBlockResult.INVALID_BLOCK: log.warning( f"Block {header_hash} at height {block.block.height} is invalid." ) return elif added == ReceiveBlockResult.DISCONNECTED_BLOCK: log.warning(f"Disconnected block {header_hash}") async with self.store.lock: tip_height = min([ head.height for head in self.blockchain.get_current_tips() ]) if (block.block.height > tip_height + self.config["sync_blocks_behind_threshold"]): async with self.store.lock: if await self.store.get_sync_mode(): return await self.store.clear_sync_info() await self.store.add_potential_tip(block.block) await self.store.set_sync_mode(True) log.info( f"We are too far behind this block. Our height is {tip_height} and block is at " f"{block.block.height}") try: # Performs sync, and catch exceptions so we don't close the connection async for ret_msg in self._sync(): yield ret_msg except asyncio.CancelledError: log.warning("Syncing failed, CancelledError") except BaseException as e: log.warning(f"Error {type(e)}{e} with syncing") finally: async for ret_msg in self._finish_sync(): yield ret_msg elif block.block.height >= tip_height - 3: log.info( f"We have received a disconnected block at height {block.block.height}, current tip is {tip_height}" ) msg = Message( "request_block", peer_protocol.RequestBlock(block.block.prev_header_hash), ) async with self.store.lock: await self.store.add_disconnected_block(block.block) yield OutboundMessage(NodeType.FULL_NODE, msg, Delivery.RESPOND) return elif added == ReceiveBlockResult.ADDED_TO_HEAD: # Only propagate blocks which extend the blockchain (becomes one of the heads) ips_changed: bool = False async with self.store.lock: log.info( f"Updated heads, new heights: {[b.height for b in self.blockchain.get_current_tips()]}" ) difficulty = self.blockchain.get_next_difficulty( block.block.prev_header_hash) next_vdf_ips = self.blockchain.get_next_ips( block.block.header_hash) log.info(f"Difficulty {difficulty} IPS {next_vdf_ips}") if next_vdf_ips != await self.store.get_proof_of_time_estimate_ips( ): await self.store.set_proof_of_time_estimate_ips( next_vdf_ips) ips_changed = True if ips_changed: rate_update = farmer_protocol.ProofOfTimeRate(next_vdf_ips) log.info(f"Sending proof of time rate {next_vdf_ips}") yield OutboundMessage( NodeType.FARMER, Message("proof_of_time_rate", rate_update), Delivery.BROADCAST, ) self.store.clear_seen_unfinished_blocks() self.store.clear_seen_blocks() assert block.block.header_block.proof_of_time assert block.block.header_block.challenge pos_quality = (block.block.header_block.proof_of_space. verify_and_get_quality()) farmer_request = farmer_protocol.ProofOfSpaceFinalized( block.block.header_block.challenge.get_hash(), block.block.height, block.block.weight, pos_quality, difficulty, ) timelord_request = timelord_protocol.ChallengeStart( block.block.header_block.challenge.get_hash(), block.block.header_block.challenge.total_weight, ) # Tell timelord to stop previous challenge and start with new one yield OutboundMessage( NodeType.TIMELORD, Message("challenge_start", timelord_request), Delivery.BROADCAST, ) # Tell full nodes about the new block yield OutboundMessage( NodeType.FULL_NODE, Message("block", block), Delivery.BROADCAST_TO_OTHERS, ) # Tell farmer about the new block yield OutboundMessage( NodeType.FARMER, Message("proof_of_space_finalized", farmer_request), Delivery.BROADCAST, ) elif added == ReceiveBlockResult.ADDED_AS_ORPHAN: assert block.block.header_block.proof_of_time assert block.block.header_block.challenge log.info( f"Received orphan block of height {block.block.header_block.challenge.height}" ) else: # Should never reach here, all the cases are covered assert False # Recursively process the next block if we have it # This code path is reached if added == ADDED_AS_ORPHAN or ADDED_TO_HEAD async with self.store.lock: next_block: Optional[ FullBlock] = await self.store.get_disconnected_block_by_prev( block.block.header_hash) if next_block is not None: async for ret_msg in self.block(peer_protocol.Block(next_block)): yield ret_msg async with self.store.lock: # Removes all temporary data for old blocks lowest_tip = min(tip.height for tip in self.blockchain.get_current_tips()) clear_height = uint32(max(0, lowest_tip - 30)) await self.store.clear_candidate_blocks_below(clear_height) await self.store.clear_unfinished_blocks_below(clear_height) await self.store.clear_disconnected_blocks_below(clear_height)