Exemple #1
0
    async def test_basic_sync(self, two_nodes):
        num_blocks = 100
        blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10)
        full_node_1, full_node_2, server_1, server_2 = two_nodes

        for i in range(1, num_blocks):
            async for _ in full_node_1.block(peer_protocol.Block(blocks[i])):
                pass

        await server_2.start_client(
            PeerInfo(server_1._host, uint16(server_1._port)), None)

        await asyncio.sleep(2)  # Allow connections to get made
        start_unf = time.time()

        while time.time() - start_unf < 60:
            # The second node should eventually catch up to the first one, and have the
            # same tip at height num_blocks - 1.
            if (max(
                [h.height for h in full_node_2.blockchain.get_current_tips()
                 ]) == num_blocks - 1):
                print(
                    f"Time taken to sync {num_blocks} is {time.time() - start_unf}"
                )

                return
            await asyncio.sleep(0.1)

        raise Exception("Took too long to process blocks")
Exemple #2
0
    async def test_blocks_load(self, two_nodes):
        num_blocks = 100
        full_node_1, full_node_2, server_1, server_2 = two_nodes
        blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10)

        await server_2.start_client(
            PeerInfo(server_1._host, uint16(server_1._port)), None
        )

        await asyncio.sleep(2)  # Allow connections to get made

        start_unf = time.time()
        for i in range(1, num_blocks):
            msg = Message("block", peer_protocol.Block(blocks[i]))
            server_1.push_message(
                OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST)
            )

        while time.time() - start_unf < 100:
            if (
                max([h.height for h in full_node_2.blockchain.get_current_tips()])
                == num_blocks - 1
            ):
                print(
                    f"Time taken to process {num_blocks} is {time.time() - start_unf}"
                )
                return
            await asyncio.sleep(0.1)

        raise Exception("Took too long to process blocks")
Exemple #3
0
    async def proof_of_time_finished(
        self, request: timelord_protocol.ProofOfTimeFinished
    ) -> OutboundMessageGenerator:
        """
        A proof of time, received by a peer timelord. We can use this to complete a block,
        and call the block routine (which handles propagation and verification of blocks).
        """
        async with self.store.lock:
            dict_key = (
                request.proof.challenge_hash,
                request.proof.number_of_iterations,
            )

            unfinished_block_obj: Optional[
                FullBlock] = await self.store.get_unfinished_block(dict_key)
            if not unfinished_block_obj:
                log.warning(
                    f"Received a proof of time that we cannot use to complete a block {dict_key}"
                )
                return
            prev_full_block = await self.store.get_block(
                unfinished_block_obj.prev_header_hash)
            assert prev_full_block
            prev_block: HeaderBlock = prev_full_block.header_block
            difficulty: uint64 = self.blockchain.get_next_difficulty(
                unfinished_block_obj.prev_header_hash)
            assert prev_block.challenge

        challenge: Challenge = Challenge(
            request.proof.challenge_hash,
            unfinished_block_obj.header_block.proof_of_space.get_hash(),
            request.proof.output.get_hash(),
            uint32(prev_block.challenge.height + 1),
            uint64(prev_block.challenge.total_weight + difficulty),
            uint64(prev_block.challenge.total_iters +
                   request.proof.number_of_iterations),
        )

        new_header_block = HeaderBlock(
            unfinished_block_obj.header_block.proof_of_space,
            request.proof,
            challenge,
            unfinished_block_obj.header_block.header,
        )
        new_full_block: FullBlock = FullBlock(new_header_block,
                                              unfinished_block_obj.body)

        async with self.store.lock:
            sync_mode = await self.store.get_sync_mode()

        if sync_mode:
            async with self.store.lock:
                await self.store.add_potential_future_block(new_full_block)
        else:
            async for msg in self.block(peer_protocol.Block(new_full_block)):
                yield msg
    async def test1(self):
        store = FullNodeStore("fndb_test")
        await store._clear_database()
        blocks = bt.get_consecutive_blocks(test_constants, 10, [], 10)
        b: Blockchain = Blockchain(test_constants)
        await store.add_block(blocks[0])
        await b.initialize({})
        for i in range(1, 9):
            assert (await
                    b.receive_block(blocks[i]
                                    )) == ReceiveBlockResult.ADDED_TO_HEAD
            await store.add_block(blocks[i])

        full_node_1 = FullNode(store, b)
        server_1 = ChiaServer(21234, full_node_1, NodeType.FULL_NODE)
        _ = await server_1.start_server("127.0.0.1", None)
        full_node_1._set_server(server_1)

        full_node_2 = FullNode(store, b)
        server_2 = ChiaServer(21235, full_node_2, NodeType.FULL_NODE)
        full_node_2._set_server(server_2)

        await server_2.start_client(PeerInfo("127.0.0.1", uint16(21234)), None)

        await asyncio.sleep(2)  # Allow connections to get made

        num_unfinished_blocks = 1000
        start_unf = time.time()
        for i in range(num_unfinished_blocks):
            msg = Message("unfinished_block",
                          peer_protocol.UnfinishedBlock(blocks[9]))
            server_1.push_message(
                OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST))

        # Send the whole block ast the end so we can detect when the node is done
        block_msg = Message("block", peer_protocol.Block(blocks[9]))
        server_1.push_message(
            OutboundMessage(NodeType.FULL_NODE, block_msg, Delivery.BROADCAST))

        while time.time() - start_unf < 300:
            if max([h.height for h in b.get_current_tips()]) == 9:
                print(
                    f"Time taken to process {num_unfinished_blocks} is {time.time() - start_unf}"
                )
                server_1.close_all()
                server_2.close_all()
                await server_1.await_closed()
                await server_2.await_closed()
                return
            await asyncio.sleep(0.1)

        server_1.close_all()
        server_2.close_all()
        await server_1.await_closed()
        await server_2.await_closed()
        raise Exception("Took too long to process blocks")
Exemple #5
0
 async def request_block(
         self, request_block: peer_protocol.RequestBlock
 ) -> OutboundMessageGenerator:
     block: Optional[FullBlock] = await self.store.get_block(
         request_block.header_hash)
     if block is not None:
         yield OutboundMessage(
             NodeType.FULL_NODE,
             Message("block", peer_protocol.Block(block)),
             Delivery.RESPOND,
         )
    async def test2(self):
        num_blocks = 100
        store = FullNodeStore("fndb_test")
        await store._clear_database()
        blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10)
        b: Blockchain = Blockchain(test_constants)
        await store.add_block(blocks[0])
        await b.initialize({})

        full_node_1 = FullNode(store, b)
        server_1 = ChiaServer(21236, full_node_1, NodeType.FULL_NODE)
        _ = await server_1.start_server("127.0.0.1", None)
        full_node_1._set_server(server_1)

        full_node_2 = FullNode(store, b)
        server_2 = ChiaServer(21237, full_node_2, NodeType.FULL_NODE)
        full_node_2._set_server(server_2)

        await server_2.start_client(PeerInfo("127.0.0.1", uint16(21236)), None)

        await asyncio.sleep(2)  # Allow connections to get made

        start_unf = time.time()
        for i in range(1, num_blocks):
            msg = Message("block", peer_protocol.Block(blocks[i]))
            server_1.push_message(
                OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST))

        while time.time() - start_unf < 300:
            if max([h.height for h in b.get_current_tips()]) == num_blocks - 1:
                print(
                    f"Time taken to process {num_blocks} is {time.time() - start_unf}"
                )
                server_1.close_all()
                server_2.close_all()
                await server_1.await_closed()
                await server_2.await_closed()
                return
            await asyncio.sleep(0.1)

        server_1.close_all()
        server_2.close_all()
        await server_1.await_closed()
        await server_2.await_closed()
        raise Exception("Took too long to process blocks")
Exemple #7
0
    async def _finish_sync(self) -> OutboundMessageGenerator:
        """
        Finalize sync by setting sync mode to False, clearing all sync information, and adding any final
        blocks that we have finalized recently.
        """
        async with self.store.lock:
            potential_fut_blocks = (
                await self.store.get_potential_future_blocks()).copy()
            await self.store.set_sync_mode(False)
            await self.store.clear_sync_info()

        for block in potential_fut_blocks:
            if self._shut_down:
                return
            async for msg in self.block(peer_protocol.Block(block)):
                yield msg

        # Update farmers and timelord with most recent information
        async for msg in self._send_challenges_to_timelords():
            yield msg
        async for msg in self._send_tips_to_farmers():
            yield msg
Exemple #8
0
    async def _on_connect(self) -> OutboundMessageGenerator:
        """
        Whenever we connect to another node, send them our current heads. Also send heads to farmers
        and challenges to timelords.
        """
        blocks: List[FullBlock] = []

        async with self.store.lock:
            heads: List[HeaderBlock] = self.blockchain.get_current_tips()
            for h in heads:
                block = await self.store.get_block(h.header.get_hash())
                assert block
                blocks.append(block)
        for block in blocks:
            request = peer_protocol.Block(block)
            yield OutboundMessage(NodeType.FULL_NODE,
                                  Message("block", request), Delivery.RESPOND)

        # Update farmers and timelord with most recent information
        async for msg in self._send_challenges_to_timelords(Delivery.RESPOND):
            yield msg
        async for msg in self._send_tips_to_farmers(Delivery.RESPOND):
            yield msg
Exemple #9
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)