Example #1
0
 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)
Example #2
0
    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)