class BtcBlockQueuingServiceTest(AbstractTestCase):
    def setUp(self):
        self.node = MockGatewayNode(
            gateway_helpers.get_gateway_opts(8000, max_block_interval=0)
        )

        self.node_connection = Mock()
        self.node_connection.is_active = MagicMock(return_value=True)
        self.node.set_known_total_difficulty = MagicMock()

        self.node.node_conn = self.node_connection

        self.block_queuing_service = BtcBlockQueuingService(self.node)
        self.node.block_queuing_service = self.block_queuing_service
        self.node.block_cleanup_service = BtcNormalBlockCleanupService(self.node, 1)
        self.block_hashes = []
        self.block_msg = self._get_sample_block()
        self.block_queuing_service.store_block_data(self.block_msg.block_hash(), self.block_msg)
        self.node.get_tx_service().track_seen_short_ids(self.block_msg.block_hash(), [])

    def _get_sample_block(self, file_path=__file__):
        root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(file_path))))
        with open(os.path.join(root_dir, "btc_sample_block.txt")) as sample_file:
            btc_block = sample_file.read().strip("\n")
        buf = bytearray(convert.hex_to_bytes(btc_block))
        parsed_block = BlockBtcMessage(buf=buf)
        return parsed_block

    def test_tracked_block_cleanup(self):
        tx_service = self.node.get_tx_service()
        self.assertEqual(1, len(tx_service.get_tracked_blocks()))
        self.node.block_cleanup_service.block_cleanup_request(self.block_msg.block_hash())
        time.time = MagicMock(return_value=time.time() + constants.MIN_SLEEP_TIMEOUT)
        self.node.alarm_queue.fire_alarms()
        self.assertEqual(0, len(tx_service.get_tracked_blocks()))
class OntBlockQueuingServiceTest(AbstractTestCase):
    def setUp(self):
        self.node = MockGatewayNode(
            gateway_helpers.get_gateway_opts(8000, max_block_interval=0))

        self.node_connection = Mock()
        self.node_connection.is_active = MagicMock(return_value=True)
        self.node.set_known_total_difficulty = MagicMock()

        self.node.node_conn = self.node_connection

        self.block_queuing_service = OntBlockQueuingService(self.node)

        self.block_hashes = []
        self.block_msg = self._get_sample_block()
        self.block_queuing_service.store_block_data(
            self.block_msg.block_hash(), self.block_msg)

    def test_get_recent_blocks(self):
        recent_blocks = list(
            self.block_queuing_service.iterate_recent_block_hashes(10))
        self.assertEqual(len(recent_blocks), 1)
        self.assertEqual(recent_blocks[0], self.block_msg.block_hash())

    def _get_sample_block(self, file_path=__file__):
        root_dir = os.path.dirname(
            os.path.dirname(os.path.dirname(os.path.abspath(file_path))))
        with open(os.path.join(root_dir,
                               "ont_sample_block.txt")) as sample_file:
            ont_block = sample_file.read().strip("\n")
        buf = bytearray(convert.hex_to_bytes(ont_block))
        parsed_block = BlockOntMessage(buf=buf)
        return parsed_block

    def test_tracked_block_cleanup(self):
        self.node.block_queuing_service = self.block_queuing_service
        tx_service = self.node.get_tx_service()
        for block_hash in self.block_queuing_service.iterate_recent_block_hashes(
                10):
            tx_service.track_seen_short_ids(block_hash, [])
        self.node._tracked_block_cleanup()
예제 #3
0
class EthBlockQueuingServiceFetchingTest(AbstractTestCase):
    def setUp(self):
        self.node = MockGatewayNode(
            gateway_helpers.get_gateway_opts(8000, max_block_interval_s=0))
        self.node.block_parts_storage = ExpiringDict(
            self.node.alarm_queue,
            gateway_constants.MAX_BLOCK_CACHE_TIME_S,
            "eth_block_queue_parts",
        )

        self.node_connection = Mock()
        self.node_connection.is_active = MagicMock(return_value=True)
        self.node.set_known_total_difficulty = MagicMock()

        self.node_conn = self.node_connection

        self.block_queuing_service = EthBlockQueuingService(
            self.node, self.node_conn)
        self.node.block_queuing_service_manager.add_block_queuing_service(
            self.node_conn, self.block_queuing_service)
        self.node_conn.enqueue_msg = MagicMock()

        self.block_hashes = []
        self.block_messages = []
        self.block_headers = []
        self.block_bodies = []

        # block numbers: 1000-1019
        prev_block_hash = None
        for i in range(20):
            block_message = InternalEthBlockInfo.from_new_block_msg(
                mock_eth_messages.new_block_eth_protocol_message(
                    i, i + 1000, prev_block_hash=prev_block_hash))
            block_hash = block_message.block_hash()
            self.block_hashes.append(block_hash)
            self.block_messages.append(block_message)

            block_parts = block_message.to_new_block_parts()
            self.block_headers.append(
                BlockHeadersEthProtocolMessage.from_header_bytes(
                    block_parts.block_header_bytes).get_block_headers()[0])
            self.block_bodies.append(
                BlockBodiesEthProtocolMessage.from_body_bytes(
                    block_parts.block_body_bytes).get_blocks()[0])

            self.node.block_queuing_service_manager.push(
                block_hash, block_message)
            prev_block_hash = block_hash

        for block_hash in self.block_hashes:
            self.block_queuing_service.remove_from_queue(block_hash)

        self.block_queuing_service.best_sent_block = (1019,
                                                      self.block_hashes[-1],
                                                      time.time())
        self.block_queuing_service.best_accepted_block = (
            1019, self.block_hashes[-1])

    def test_get_block_hashes_from_hash(self):
        # request: start: 1019, count: 1
        # result: 1019
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash(
            self.block_hashes[19], 1, 0, False)
        self.assertTrue(success)
        self.assertEqual(1, len(hashes))
        self.assertEqual(self.block_hashes[19], hashes[0])

        # request: start: 1010, count: 10
        # result: 1010, 1011, .. 1019
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash(
            self.block_hashes[10], 10, 0, False)
        self.assertTrue(success)
        self.assertEqual(10, len(hashes))
        for i, block_hash in enumerate(hashes):
            self.assertEqual(self.block_hashes[i + 10], hashes[i])

        # request: start: 1010, count: 2, skip: 5
        # result: 1010, 1016
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash(
            self.block_hashes[10], 2, 5, False)
        self.assertTrue(success)
        self.assertEqual(2, len(hashes))
        self.assertEqual(self.block_hashes[10], hashes[0])
        self.assertEqual(self.block_hashes[16], hashes[1])

        # request: start: 1010, count: 2, skip: 5, reverse = True
        # result: 1010, 1004
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash(
            self.block_hashes[10], 2, 5, True)
        self.assertTrue(success)
        self.assertEqual(2, len(hashes))
        self.assertEqual(self.block_hashes[10], hashes[0])
        self.assertEqual(self.block_hashes[4], hashes[1])

    def test_get_block_hashes_from_height(self):
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height(
            1019, 1, 0, False)
        self.assertTrue(success)
        self.assertEqual(1, len(hashes))
        self.assertEqual(self.block_hashes[19], hashes[0])

    def test_get_block_by_hash_orphaned_not_found(self):
        # create fork at block 17
        block_message = InternalEthBlockInfo.from_new_block_msg(
            mock_eth_messages.new_block_eth_protocol_message(21, 1017))
        block_hash = block_message.block_hash()
        self.block_queuing_service.push(block_hash, block_message)
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash(
            block_hash, 1, 0, False)
        self.assertTrue(success)
        self.assertEqual(0, len(hashes))

    def test_get_block_by_number_ambiguous_succeeds(self):
        # create fork at block 17
        block_message = InternalEthBlockInfo.from_new_block_msg(
            mock_eth_messages.new_block_eth_protocol_message(21, 1017))
        block_hash = block_message.block_hash()
        self.block_queuing_service.push(block_hash, block_message)

        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height(
            1017, 1, 0, False)
        self.assertTrue(success)
        self.assertEqual(self.block_hashes[17], hashes[0])

    def test_get_block_hashes_fork_follows_last_sent(self):
        # create fork at block 17
        block_message = InternalEthBlockInfo.from_new_block_msg(
            mock_eth_messages.new_block_eth_protocol_message(21, 1017))
        block_hash = block_message.block_hash()
        self.block_queuing_service.push(block_hash, block_message)
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_hash(
            self.block_hashes[15], 5, 0, False)
        self.assertTrue(success)
        self.assertEqual(5, len(hashes))
        self.assertEqual(self.block_hashes[15:20], hashes)

    def test_get_blocks_from_height_not_all_found_at_end(self):
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height(
            1018, 5, 0, False)
        self.assertTrue(success)
        self.assertEqual(2, len(hashes))
        self.assertEqual(self.block_hashes[18], hashes[0])
        self.assertEqual(self.block_hashes[19], hashes[1])

    def test_get_blocks_from_height_not_all_found_at_beginning(self):
        success, hashes = self.block_queuing_service.get_block_hashes_starting_from_height(
            950, 5, 0, False)
        self.assertFalse(success)
        self.assertEqual(0, len(hashes))

    def test_iterate_recent_block_hashes(self):
        top_blocks = list(
            self.block_queuing_service.iterate_recent_block_hashes(
                max_count=10))
        block_hash = top_blocks[0]
        self.assertEqual(
            self.block_queuing_service._height_by_block_hash[block_hash],
            self.block_queuing_service._highest_block_number)
        self.assertEqual(10, len(top_blocks))

    def test_get_transactions_hashes_from_message(self):
        last_block_hash = list(
            self.block_queuing_service._block_hashes_by_height[
                self.block_queuing_service._highest_block_number])[0]
        self.assertIsNotNone(
            self.block_queuing_service.get_block_body_from_message(
                last_block_hash))
        self.assertIsNone(
            self.block_queuing_service.get_block_body_from_message(bytes(64)))

    def test_tracked_block_cleanup(self):
        from bxgateway.services.eth.eth_normal_block_cleanup_service import EthNormalBlockCleanupService
        self.node.block_cleanup_service = EthNormalBlockCleanupService(
            self.node, 1)
        tx_service = self.node.get_tx_service()
        for block_hash in self.block_queuing_service.iterate_recent_block_hashes(
                self.node.network.block_confirmations_count + 2):
            tx_service.track_seen_short_ids(block_hash, [])

        self.node.block_cleanup_service.block_cleanup_request = MagicMock()
        self.node._tracked_block_cleanup()
        self.node.block_cleanup_service.block_cleanup_request.assert_called_once(
        )

    def test_try_send_headers_to_node_success(self):
        self.node_conn.enqueue_msg.reset_mock()
        result = self.block_queuing_service.try_send_headers_to_node(
            self.block_hashes[:4])

        self.assertTrue(result)
        self.node_conn.enqueue_msg.assert_called_once_with(
            BlockHeadersEthProtocolMessage(None, self.block_headers[:4]))

    def test_try_send_headers_to_node_unknown_block(self):
        self.node.broadcast = MagicMock()
        result = self.block_queuing_service.try_send_headers_to_node(
            [self.block_hashes[0],
             helpers.generate_object_hash()])

        self.assertFalse(result)
        self.node.broadcast.assert_not_called()

    def test_try_send_bodies_to_node_success(self):
        self.node_conn.enqueue_msg.reset_mock()
        result = self.block_queuing_service.try_send_bodies_to_node(
            self.block_hashes[:4])

        self.assertTrue(result)
        self.node_conn.enqueue_msg.assert_called_once_with(
            BlockBodiesEthProtocolMessage(None, self.block_bodies[:4]))

    def test_try_send_bodies_to_node_unknown_block(self):
        self.node_conn.enqueue_msg.reset_mock()
        result = self.block_queuing_service.try_send_bodies_to_node(
            [self.block_hashes[0],
             helpers.generate_object_hash()])

        self.assertFalse(result)
        self.node_conn.enqueue_msg.assert_not_called()
예제 #4
0
class EthWsProxyPublisherTest(AbstractTestCase):
    @async_test
    async def setUp(self) -> None:
        crypto_utils.recover_public_key = MagicMock(return_value=bytes(32))
        account_model = BdnAccountModelBase(
            "account_id",
            "account_name",
            "fake_certificate",
            new_transaction_streaming=BdnServiceModelConfigBase(
                expire_date=date(2999, 1, 1).isoformat()))

        self.eth_ws_port = helpers.get_free_port()
        self.eth_ws_uri = f"ws://127.0.0.1:{self.eth_ws_port}"
        self.eth_ws_server_message_queue = asyncio.Queue()
        await self.start_server()

        gateway_opts = gateway_helpers.get_gateway_opts(
            8000, eth_ws_uri=self.eth_ws_uri, ws=True)
        gateway_opts.set_account_options(account_model)
        self.gateway_node = MockGatewayNode(gateway_opts)
        self.gateway_node.transaction_streamer_peer = OutboundPeerModel(
            "127.0.0.1", 8006, node_type=NodeType.INTERNAL_GATEWAY)
        self.gateway_node.feed_manager.register_feed(
            EthPendingTransactionFeed(self.gateway_node.alarm_queue))

        self.eth_ws_proxy_publisher = EthWsProxyPublisher(
            self.eth_ws_uri, self.gateway_node.feed_manager,
            self.gateway_node.get_tx_service(), self.gateway_node)
        self.subscriber: Subscriber[
            RawTransactionFeedEntry] = self.gateway_node.feed_manager.subscribe_to_feed(
                EthPendingTransactionFeed.NAME, {})
        self.assertIsNotNone(self.subscriber)

        await self.eth_ws_proxy_publisher.start()
        await asyncio.sleep(0.01)

        self.assertEqual(len(self.eth_ws_proxy_publisher.receiving_tasks), 2)
        self.assertEqual(0, self.subscriber.messages.qsize())

        self.sample_transactions = {
            i: mock_eth_messages.get_dummy_transaction(i)
            for i in range(10)
        }

    async def start_server(self) -> None:
        self.eth_test_ws_server = await websockets.serve(
            self.ws_test_serve, constants.LOCALHOST, self.eth_ws_port)

    async def ws_test_serve(self, websocket, path):
        async def consumer(ws, _path):
            try:
                async for message in ws:
                    rpc_request = JsonRpcRequest.from_jsons(message)
                    if rpc_request.method_name == "eth_subscribe":
                        await ws.send(
                            JsonRpcResponse(
                                rpc_request.id,
                                str(rpc_request.params[0])).to_jsons())
                    elif rpc_request.method_name == "eth_getTransactionByHash":
                        nonce = int(rpc_request.id)
                        await ws.send(
                            JsonRpcResponse(
                                rpc_request.id,
                                tx_to_eth_rpc_json(
                                    self.sample_transactions[nonce])).to_jsons(
                                    ))
            except Exception:
                # server closed, exit
                pass

        async def producer(ws, _path):
            try:
                while True:
                    subscription, message = await self.eth_ws_server_message_queue.get(
                    )
                    await ws.send(
                        JsonRpcRequest(
                            None,
                            "eth_subscription",
                            {
                                "subscription": subscription,
                                "result": message
                            },
                        ).to_jsons())
            except Exception:
                # server closed, exit
                pass

        consumer_task = asyncio.create_task(consumer(websocket, path))
        producer_task = asyncio.create_task(producer(websocket, path))
        done, pending = await asyncio.wait(
            [consumer_task, producer_task],
            return_when=asyncio.FIRST_COMPLETED,
        )
        for task in pending:
            task.cancel()

    @async_test
    async def test_subscription(self):
        tx_hash = helpers.generate_object_hash()
        tx_contents = mock_eth_messages.get_dummy_transaction(1)
        self.gateway_node.get_tx_service().set_transaction_contents(
            tx_hash, rlp.encode(tx_contents))

        tx_hash_2 = helpers.generate_object_hash()
        tx_contents_2 = mock_eth_messages.get_dummy_transaction(2)
        self.gateway_node.get_tx_service().set_transaction_contents(
            tx_hash_2, rlp.encode(tx_contents_2))

        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}"))
        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash_2.binary)}"))
        await asyncio.sleep(0.01)

        self.assertEqual(2, self.subscriber.messages.qsize())

        tx_message_1 = await self.subscriber.receive()
        self.assertEqual(f"0x{convert.bytes_to_hex(tx_hash.binary)}",
                         tx_message_1["tx_hash"])
        self.assertEqual(tx_contents.to_json(), tx_message_1["tx_contents"])

        tx_message_2 = await self.subscriber.receive()
        self.assertEqual(f"0x{convert.bytes_to_hex(tx_hash_2.binary)}",
                         tx_message_2["tx_hash"])
        self.assertEqual(tx_contents_2.to_json(), tx_message_2["tx_contents"])

    @async_test
    async def test_subscription_with_no_content_filled(self):
        self.maxDiff = None
        tx_hash = helpers.generate_hash()
        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash)}"))

        await asyncio.sleep(0.01)

        self.assertEqual(1, self.subscriber.messages.qsize())
        tx_message = await self.subscriber.receive()
        self.assertEqual(f"0x{convert.bytes_to_hex(tx_hash)}",
                         tx_message["tx_hash"])

        expected_contents = self.sample_transactions[3].to_json()
        self.assertEqual(expected_contents, tx_message["tx_contents"])

    @patch("bxcommon.constants.WS_RECONNECT_TIMEOUTS", [0.01])
    @patch("bxcommon.constants.WS_MIN_RECONNECT_TIMEOUT_S", 0)
    @async_test
    async def test_disconnect_server(self):
        tx_hash = helpers.generate_object_hash()
        tx_contents = mock_eth_messages.get_dummy_transaction(1)
        self.gateway_node.get_tx_service().set_transaction_contents(
            tx_hash, rlp.encode(tx_contents))

        self.eth_test_ws_server.close()
        await self.eth_test_ws_server.wait_closed()
        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}"))

        await asyncio.sleep(0.05)

        self.assertEqual(0, self.subscriber.messages.qsize())
        self.assertFalse(self.eth_ws_proxy_publisher.running)

    @patch("bxcommon.constants.WS_RECONNECT_TIMEOUTS", [0.01, 0.05])
    @patch("bxcommon.constants.WS_MIN_RECONNECT_TIMEOUT_S", 0)
    @async_test
    async def test_disconnect_server_reconnect(self):
        tx_hash = helpers.generate_object_hash()
        tx_contents = mock_eth_messages.get_dummy_transaction(1)
        self.gateway_node.get_tx_service().set_transaction_contents(
            tx_hash, rlp.encode(tx_contents))

        self.eth_test_ws_server.close()
        await self.eth_test_ws_server.wait_closed()
        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}"))

        await asyncio.sleep(0.02)

        self.assertEqual(0, self.subscriber.messages.qsize())
        self.assertTrue(self.eth_ws_proxy_publisher.running)
        self.assertFalse(self.eth_ws_proxy_publisher.connected_event.is_set())

        await self.start_server()
        await asyncio.sleep(0.1)

        self.assertTrue(self.eth_ws_proxy_publisher.connected_event.is_set())
        self.assertEqual(0, self.subscriber.messages.qsize())

        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}"))
        await asyncio.sleep(0.01)
        self.assertEqual(1, self.subscriber.messages.qsize())

    @patch("bxcommon.constants.WS_RECONNECT_TIMEOUTS", [0.01])
    @patch("bxcommon.constants.WS_MIN_RECONNECT_TIMEOUT_S", 0)
    @async_test
    async def test_disconnect_server_revive(self):
        tx_hash = helpers.generate_object_hash()
        tx_contents = mock_eth_messages.get_dummy_transaction(1)
        self.gateway_node.get_tx_service().set_transaction_contents(
            tx_hash, rlp.encode(tx_contents))

        self.eth_test_ws_server.close()
        await self.eth_test_ws_server.wait_closed()
        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}"))

        await asyncio.sleep(0.05)

        self.assertEqual(0, self.subscriber.messages.qsize())
        self.assertFalse(self.eth_ws_proxy_publisher.running)

        await self.start_server()
        await asyncio.sleep(0)

        await self.eth_ws_proxy_publisher.revive()
        await self.eth_ws_server_message_queue.put(
            (TX_SUB_ID, f"0x{convert.bytes_to_hex(tx_hash.binary)}"))
        await asyncio.sleep(0.01)
        self.assertEqual(1, self.subscriber.messages.qsize())

    @async_test
    async def tearDown(self) -> None:
        await self.eth_ws_proxy_publisher.stop()
        self.eth_test_ws_server.close()
        await self.eth_test_ws_server.wait_closed()
        crypto_utils.recover_public_key = _recover_public_key
예제 #5
0
class AbstractGatewayRpcIntegrationTest(AbstractTestCase):
    def __init__(self, *args, **kwargs):
        # hack to avoid unit test discovery of this class
        super().__init__(*args, **kwargs)
        if self.__class__ != AbstractGatewayRpcIntegrationTest:
            # pylint: disable=no-value-for-parameter
            self.run = unittest.TestCase.run.__get__(self, self.__class__)
        else:
            self.run = lambda self, *args, **kwargs: None
        self._account_model = None

    # pyre-ignore async setup problems
    async def setUp(self) -> None:
        self.gateway_node = MockGatewayNode(self.get_gateway_opts())
        self.gateway_node.requester = ThreadedRequestService(
            "mock_thread_service", self.gateway_node.alarm_queue,
            constants.THREADED_HTTP_POOL_SLEEP_INTERVAL_S)
        self.gateway_node.requester.start()
        self.gateway_node.account_id = ACCOUNT_ID
        self.node_endpoint_1 = IpEndpoint("127.0.0.1", 7000)
        self.blockchain_connection_1 = EthBaseConnection(
            MockSocketConnection(node=self.gateway_node,
                                 ip_address="127.0.0.1",
                                 port=7000),
            cast(EthGatewayNode, self.gateway_node))
        self.blockchain_connection_1.state = ConnectionState.ESTABLISHED

    @abstractmethod
    def get_gateway_opts(self) -> GatewayOpts:
        feed_service_model = FeedServiceModelBase(allow_filtering=True,
                                                  available_fields=["all"])
        base_feed_service_model = BdnFeedServiceModelConfigBase(
            expire_date="2999-01-01", feed=feed_service_model)
        self._account_model = BdnAccountModelBase(
            account_id="",
            logical_account_name="",
            certificate="",
            expire_date=utils_constants.DEFAULT_EXPIRATION_DATE.isoformat(),
            cloud_api=BdnServiceModelConfigBase(
                msg_quota=None,
                permit=BdnServiceModelBase(service_type=BdnServiceType.PERMIT),
                expire_date=utils_constants.DEFAULT_EXPIRATION_DATE.isoformat(
                )),
            new_transaction_streaming=base_feed_service_model,
            new_pending_transaction_streaming=base_feed_service_model,
            on_block_feed=base_feed_service_model,
            new_block_streaming=base_feed_service_model,
            transaction_state_feed=base_feed_service_model,
            blockchain_protocol="Ethereum",
            blockchain_network="Mainnet",
        )

    @abstractmethod
    async def request(self, req: BxJsonRpcRequest) -> JsonRpcResponse:
        pass

    @async_test
    async def test_blxr_tx(self):
        result = await self.request(
            BxJsonRpcRequest(
                "1", RpcRequestType.BLXR_TX, {
                    rpc_constants.TRANSACTION_PARAMS_KEY: RAW_TRANSACTION_HEX,
                    rpc_constants.STATUS_TRACK_PARAMS_KEY: "True"
                }))
        self.assertEqual("1", result.id)
        self.assertIsNone(result.error)
        self.assertEqual(TRANSACTION_HASH, result.result["tx_hash"])
        self.assertEqual(ACCOUNT_ID, result.result["account_id"])
        self.assertEqual(TransactionFlag.PAID_TX.name.lower(),
                         result.result["transaction_flag"].lower())

        self.assertEqual(1, len(self.gateway_node.broadcast_messages))
        self.assertEqual(Sha256Hash(convert.hex_to_bytes(TRANSACTION_HASH)),
                         self.gateway_node.broadcast_messages[0][0].tx_hash())

    @async_test
    async def test_blxr_tx_expired(self):
        self.gateway_node.account_model.is_account_valid = MagicMock(
            return_value=False)
        result = await self.request(
            BxJsonRpcRequest(
                "2", RpcRequestType.BLXR_TX, {
                    rpc_constants.TRANSACTION_PARAMS_KEY: RAW_TRANSACTION_HEX,
                }))
        self.assertEqual("2", result.id)
        self.assertEqual("Invalid Account ID", result.error.message)
        self.assertIsNone(result.result)
        self.assertIsNotNone(result.error)

    @async_test
    async def test_blxr_tx_quota_exceeded(self):
        self.gateway_node.quota_level = 100
        result = await self.request(
            BxJsonRpcRequest(
                "3", RpcRequestType.BLXR_TX, {
                    rpc_constants.TRANSACTION_PARAMS_KEY: RAW_TRANSACTION_HEX,
                }))
        self.assertEqual("3", result.id)
        self.assertEqual("Insufficient quota", result.error.message)
        self.assertIsNone(result.result)
        self.assertIsNotNone(result.error)

    @async_test
    async def test_blxr_tx_from_json(self):
        tx_json = {
            'from':
            '0xc165599b5e418bb9d8a19090696ea2403b2927ed',
            'gas':
            "0x5208",
            'gasPrice':
            "0xc1b759d70",
            'hash':
            '0xd569674ad9fcaaedcb6867b7896067b445d4a838316be4292c474df17bf4bd50',
            'input':
            '0x',
            'nonce':
            "0x14",
            'to':
            '0xdb5f0c1f4198bc6ffa98b35f7188f82740b8caf7',
            'value':
            "0x3e871b540c000",
            'v':
            '0x26',
            'r':
            '0x484bc950fb2d595500baa604774cb8d156a677198e087801936f38ca0b27049',
            's':
            '0x7ca82ef7b6938c2e96966dd940b186c9cdc0c7f42b2843adbc0751bb6e67a2d4'
        }

        result = await self.request(
            BxJsonRpcRequest(
                "1", RpcRequestType.BLXR_TX, {
                    rpc_constants.TRANSACTION_JSON_PARAMS_KEY: tx_json,
                }))

        self.assertEqual("1", result.id)
        self.assertIsNone(result.error)
        self.assertEqual(
            "ad6f9332384194f80d8e49af8f093ad019b3f6b7173eb2956a46c9a0d8c4d03c",
            result.result["tx_hash"])
        self.assertEqual(ACCOUNT_ID, result.result["account_id"])
        self.assertEqual(TransactionFlag.PAID_TX.name.lower(),
                         result.result["transaction_flag"].lower())

        self.assertEqual(1, len(self.gateway_node.broadcast_messages))
        self.assertEqual(
            Sha256Hash(
                convert.hex_to_bytes(
                    "ad6f9332384194f80d8e49af8f093ad019b3f6b7173eb2956a46c9a0d8c4d03c"
                )), self.gateway_node.broadcast_messages[0][0].tx_hash())

    @async_test
    async def test_blxr_eth_call(self):
        self.gateway_node.eth_ws_proxy_publisher = MockEthWsProxyPublisher(
            None, None, None, None)
        self.gateway_node.eth_ws_proxy_publisher.call_rpc = AsyncMock(
            return_value=JsonRpcResponse(request_id=1))
        result = await self.request(
            BxJsonRpcRequest(
                "1", RpcRequestType.BLXR_ETH_CALL, {
                    rpc_constants.TRANSACTION_JSON_PARAMS_KEY:
                    TRANSACTION_JSON,
                    rpc_constants.TAG_PARAMS_KEY: "latest"
                }))
        self.gateway_node.eth_ws_proxy_publisher.call_rpc.mock.assert_called_with(
            "eth_call", [TRANSACTION_JSON, "latest"])
        self.assertIsNone(result.error)

    @async_test
    async def test_gateway_status(self):
        result_summary = await self.request(
            BxJsonRpcRequest("2", RpcRequestType.GATEWAY_STATUS, None))
        self.assertEqual("2", result_summary.id)
        self.assertIsNone(result_summary.error)
        self.assertEqual(constants.LOCALHOST,
                         result_summary.result["ip_address"])
        self.assertEqual("NA", result_summary.result["continent"])
        self.assertEqual("United States", result_summary.result["country"])

        result_detailed = await self.request(
            BxJsonRpcRequest(
                "2.5", RpcRequestType.GATEWAY_STATUS, {
                    rpc_constants.DETAILS_LEVEL_PARAMS_KEY:
                    GatewayStatusDetailsLevel.DETAILED.name
                }))
        self.assertEqual("2.5", result_detailed.id)
        self.assertIsNone(result_detailed.error)
        self.assertEqual(constants.LOCALHOST,
                         result_detailed.result["summary"]["ip_address"])
        self.assertEqual("NA", result_detailed.result["summary"]["continent"])
        self.assertEqual("United States",
                         result_detailed.result["summary"]["country"])

    @async_test
    async def test_stop(self):
        result = await self.request(
            BxJsonRpcRequest("3", RpcRequestType.STOP, None))
        self.assertEqual("3", result.id)
        self.assertIsNone(result.error)
        self.assertEqual({}, result.result)
        self.gateway_node.should_force_exit = True
        self.gateway_node.should_restart_on_high_memory = False

    @async_test
    async def test_memory(self):
        result = await self.request(
            BxJsonRpcRequest("4", RpcRequestType.MEMORY, None))
        self.assertEqual("4", result.id)
        self.assertIsNone(result.error)
        self.assertEqual(
            0, result.result[gateway_memory_rpc_request.TOTAL_CACHED_TX])
        self.assertEqual(
            "0 bytes",
            result.result[gateway_memory_rpc_request.TOTAL_CACHED_TX_SIZE])

    @async_test
    async def test_peers(self):
        result = await self.request(
            BxJsonRpcRequest("5", RpcRequestType.PEERS, None))
        self.assertEqual("5", result.id)
        self.assertIsNone(result.error)
        self.assertEqual([], result.result)

    @async_test
    async def test_bdn_performance_single_node(self):
        self.gateway_node.connection_pool.add(20,
                                              self.node_endpoint_1.ip_address,
                                              self.node_endpoint_1.port,
                                              self.blockchain_connection_1)
        gateway_bdn_performance_stats_service.set_node(self.gateway_node)
        gateway_bdn_performance_stats_service.log_block_from_bdn()
        gateway_bdn_performance_stats_service.log_block_from_bdn()
        gateway_bdn_performance_stats_service.log_block_from_blockchain_node(
            self.node_endpoint_1)
        gateway_bdn_performance_stats_service.log_tx_from_bdn()
        gateway_bdn_performance_stats_service.log_tx_from_blockchain_node(
            self.node_endpoint_1)
        gateway_bdn_performance_stats_service.log_tx_from_blockchain_node(
            self.node_endpoint_1)
        gateway_bdn_performance_stats_service.close_interval_data()

        result = await self.request(
            BxJsonRpcRequest("6", RpcRequestType.BDN_PERFORMANCE, None))
        self.assertEqual("6", result.id)
        self.assertIsNone(result.error)
        node_stats = result.result[str(self.node_endpoint_1)]
        self.assertEqual("66.67%", node_stats["blocks_from_bdn_percentage"])
        self.assertEqual("33.33%",
                         node_stats["transactions_from_bdn_percentage"])
        self.assertEqual(3, node_stats["total_blocks_seen"])

    @async_test
    async def test_bdn_performance_multi_node(self):
        self.gateway_node.connection_pool.add(20,
                                              self.node_endpoint_1.ip_address,
                                              self.node_endpoint_1.port,
                                              self.blockchain_connection_1)

        blockchain_connection_2 = EthBaseConnection(
            MockSocketConnection(node=self.gateway_node,
                                 ip_address="127.0.0.1",
                                 port=333), self.gateway_node)
        blockchain_connection_2.state = ConnectionState.ESTABLISHED
        self.gateway_node.mock_add_blockchain_peer(blockchain_connection_2)
        node_endpoint_2 = IpEndpoint("127.0.0.1", 333)
        self.gateway_node.connection_pool.add(21, node_endpoint_2.ip_address,
                                              node_endpoint_2.port,
                                              blockchain_connection_2)

        gateway_bdn_performance_stats_service.set_node(self.gateway_node)
        gateway_bdn_performance_stats_service.log_block_from_bdn()
        gateway_bdn_performance_stats_service.log_block_from_bdn()
        gateway_bdn_performance_stats_service.log_block_from_blockchain_node(
            self.node_endpoint_1)
        gateway_bdn_performance_stats_service.log_tx_from_bdn()
        gateway_bdn_performance_stats_service.log_tx_from_blockchain_node(
            self.node_endpoint_1)
        gateway_bdn_performance_stats_service.log_tx_from_blockchain_node(
            self.node_endpoint_1)
        gateway_bdn_performance_stats_service.close_interval_data()

        result = await self.request(
            BxJsonRpcRequest("6", RpcRequestType.BDN_PERFORMANCE, None))
        self.assertEqual("6", result.id)
        self.assertIsNone(result.error)
        node_stats = result.result[str(self.node_endpoint_1)]
        self.assertEqual("66.67%", node_stats["blocks_from_bdn_percentage"])
        self.assertEqual("33.33%",
                         node_stats["transactions_from_bdn_percentage"])
        self.assertEqual(3, node_stats["total_blocks_seen"])

        node_stats_2 = result.result[str(node_endpoint_2)]
        self.assertEqual("100.00%", node_stats_2["blocks_from_bdn_percentage"])
        self.assertEqual("100.00%",
                         node_stats_2["transactions_from_bdn_percentage"])
        self.assertEqual(3, node_stats_2["total_blocks_seen"])

    @async_test
    async def test_transaction_status(self):
        time.time = MagicMock(return_value=time.time())
        expected_assignment_time = datetime.datetime.fromtimestamp(
            time.time()).isoformat()

        short_id = 123
        transaction_hash = helpers.generate_object_hash()
        transaction_contents = helpers.generate_bytearray(250)

        tx_service = self.gateway_node.get_tx_service()
        transaction_key = tx_service.get_transaction_key(transaction_hash)
        tx_service.set_transaction_contents_by_key(transaction_key,
                                                   transaction_contents)
        tx_service.assign_short_id_by_key(transaction_key, short_id)

        result = await self.request(
            BxJsonRpcRequest("7", RpcRequestType.TX_STATUS, {
                "transaction_hash":
                convert.bytes_to_hex(transaction_hash.binary)
            }))
        self.assertEqual("7", result.id)
        self.assertIsNone(result.error)
        self.assertEqual(
            {
                "status": "assigned short ID",
                "short_ids": [123],
                "assignment_time": expected_assignment_time
            }, result.result)

    @async_test
    async def test_add_blockchain_peer(self):
        self.gateway_node.enqueue_connection = MagicMock()
        result = await self.request(
            BxJsonRpcRequest(
                "8", RpcRequestType.ADD_BLOCKCHAIN_PEER, {
                    "peer":
                    "enode://d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396@127.0.0.1:30302"
                }))
        self.gateway_node.enqueue_connection.assert_called_once_with(
            "127.0.0.1", 30302, ConnectionType.BLOCKCHAIN_NODE)
        self.assertIn(BlockchainPeerInfo("127.0.0.1", 30302),
                      self.gateway_node.blockchain_peers)
        self.assertEqual("8", result.id)
        self.assertIsNone(result.error)
        self.assertEqual({"new_peer": "127.0.0.1:30302"}, result.result)

    @async_test
    async def test_remove_blockchain_peer(self):
        conn = MockConnection(
            MockSocketConnection(9, ip_address="127.0.0.1", port=30302),
            self.gateway_node)
        conn.mark_for_close = MagicMock()
        self.gateway_node.connection_pool.add(9, "127.0.0.1", 30302, conn)
        self.gateway_node.blockchain_peers.add(
            BlockchainPeerInfo(
                "127.0.0.1", 30302,
                "d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396"
            ))
        result = await self.request(
            BxJsonRpcRequest(
                "9", RpcRequestType.REMOVE_BLOCKCHAIN_PEER, {
                    "peer":
                    "enode://d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396@127.0.0.1:30302"
                }))
        self.assertNotIn(BlockchainPeerInfo("127.0.0.1", 30302),
                         self.gateway_node.blockchain_peers)
        conn.mark_for_close.assert_called_once_with(False)
        self.assertEqual("9", result.id)
        self.assertIsNone(result.error)
        self.assertEqual({"removed_peer": "127.0.0.1:30302"}, result.result)

    @async_test
    async def test_remove_blockchain_peer_not_in_pool(self):
        self.gateway_node.alarm_queue.register_alarm = MagicMock()
        self.gateway_node.blockchain_peers.add(
            BlockchainPeerInfo(
                "127.0.0.1", 30302,
                "d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396"
            ))
        result = await self.request(
            BxJsonRpcRequest(
                "9", RpcRequestType.REMOVE_BLOCKCHAIN_PEER, {
                    "peer":
                    "enode://d76d7d11a822fab02836f8b0ea462205916253eb630935d15191fb6f9d218cd94a768fc5b3d5516b9ed5010a4765f95aea7124a39d0ab8aaf6fa3d57e21ef396@127.0.0.1:30302"
                }))
        self.assertNotIn(BlockchainPeerInfo("127.0.0.1", 30302),
                         self.gateway_node.blockchain_peers)
        self.gateway_node.alarm_queue.register_alarm.assert_called()
        self.assertEqual("9", result.id)
        self.assertIsNone(result.error)
        self.assertEqual({"removed_peer": "127.0.0.1:30302"}, result.result)
class EthNodeConnectionProtocolTest(AbstractTestCase):
    def setUp(self) -> None:

        opts = gateway_helpers.get_gateway_opts(8000, include_default_eth_args=True)

        self.node = MockGatewayNode(opts)

        self.connection = MagicMock(spec=AbstractGatewayBlockchainConnection)
        self.connection.node = self.node
        self.connection.is_active = MagicMock(return_value=True)
        self.connection.network_num = 5
        self.connection.format_connection_desc = "127.0.0.1:12345 - B"
        self.node.set_known_total_difficulty = MagicMock()
        self.node.node_conn = self.connection
        self.node.message_converter = EthNormalMessageConverter()

        self.cleanup_service = EthNormalBlockCleanupService(
            self.node, NETWORK_NUM
        )
        self.node.block_cleanup_service = self.cleanup_service
        self.node.block_processing_service = EthBlockProcessingService(
            self.node
        )
        self.node.block_processing_service.queue_block_for_processing = MagicMock()
        self.node.block_queuing_service = EthBlockQueuingService(self.node)

        dummy_private_key = crypto_utils.make_private_key(
            helpers.generate_bytearray(111)
        )
        dummy_public_key = crypto_utils.private_to_public_key(dummy_private_key)
        self.sut = EthNodeConnectionProtocol(
            self.connection, True, dummy_private_key, dummy_public_key
        )
        self.sut._waiting_checkpoint_headers_request = False

    def test_request_block_bodies(self):
        self.cleanup_service.clean_block_transactions_by_block_components = (
            MagicMock()
        )

        block_hashes_sets = [
            [helpers.generate_object_hash(), helpers.generate_object_hash()],
            [helpers.generate_object_hash()],
            [helpers.generate_object_hash()],
        ]

        block_bodies_messages = [
            BlockBodiesEthProtocolMessage(
                None,
                [
                    mock_eth_messages.get_dummy_transient_block_body(1),
                    mock_eth_messages.get_dummy_transient_block_body(2),
                ],
            ),
            BlockBodiesEthProtocolMessage(
                None, [mock_eth_messages.get_dummy_transient_block_body(3)]
            ),
            BlockBodiesEthProtocolMessage(
                None, [mock_eth_messages.get_dummy_transient_block_body(4)]
            ),
        ]

        transactions = [
            [
                [
                    tx.hash()
                    for tx in BlockBodiesEthProtocolMessage.from_body_bytes(
                        block_body_bytes
                    )
                    .get_blocks()[0]
                    .transactions
                ]
                for block_body_bytes in msg.get_block_bodies_bytes()
            ]
            for msg in block_bodies_messages
        ]

        for block_bodies_message in block_bodies_messages:
            block_bodies_message.serialize()

        for block_hashes_set in block_hashes_sets:
            for block_hash in block_hashes_set:
                self.node.block_cleanup_service._block_hash_marked_for_cleanup.add(
                    block_hash
                )
            self.sut.request_block_body(block_hashes_set)

        self.sut.msg_block_bodies(block_bodies_messages[0])
        call_args_list = (
            self.cleanup_service.clean_block_transactions_by_block_components.call_args_list
        )

        first_call = call_args_list[0]
        first_kwargs = first_call[1]
        self.assertEqual(
            first_kwargs["transaction_service"], self.node.get_tx_service()
        )
        self.assertEqual(first_kwargs["block_hash"], block_hashes_sets[0][0])
        self.assertEqual(
            list(first_kwargs["transactions_list"]), transactions[0][0]
        )

        second_call = call_args_list[1]
        second_kwargs = second_call[1]
        self.assertEqual(
            second_kwargs["transaction_service"], self.node.get_tx_service()
        )
        self.assertEqual(second_kwargs["block_hash"], block_hashes_sets[0][1])
        self.assertEqual(
            list(second_kwargs["transactions_list"]), transactions[0][1]
        )

        self.cleanup_service.clean_block_transactions_by_block_components.reset_mock()
        self.sut.msg_block_bodies(block_bodies_messages[1])
        call_args_list = (
            self.cleanup_service.clean_block_transactions_by_block_components.call_args_list
        )

        first_call = call_args_list[0]
        first_kwargs = first_call[1]
        self.assertEqual(
            first_kwargs["transaction_service"], self.node.get_tx_service()
        )
        self.assertEqual(first_kwargs["block_hash"], block_hashes_sets[1][0])
        self.assertEqual(
            list(first_kwargs["transactions_list"]), transactions[1][0]
        )

        self.cleanup_service.clean_block_transactions_by_block_components.reset_mock()
        self.sut.msg_block_bodies(block_bodies_messages[2])
        call_args_list = (
            self.cleanup_service.clean_block_transactions_by_block_components.call_args_list
        )

        first_call = call_args_list[0]
        first_kwargs = first_call[1]
        self.assertEqual(
            first_kwargs["transaction_service"], self.node.get_tx_service()
        )
        self.assertEqual(first_kwargs["block_hash"], block_hashes_sets[2][0])
        self.assertEqual(
            list(first_kwargs["transactions_list"]), transactions[2][0]
        )

    def test_msg_get_block_headers_unknown(self):
        block_hash = helpers.generate_hash()
        self.sut.msg_proxy_request = MagicMock()
        message = GetBlockHeadersEthProtocolMessage(None, block_hash, 1, 0, 0)

        self.sut.msg_get_block_headers(message)
        self.sut.msg_proxy_request.assert_called_once()

    def test_msg_get_block_headers_known_single(self):
        header = mock_eth_messages.get_dummy_block_header(12)
        block = mock_eth_messages.get_dummy_block(1, header)
        raw_hash = header.hash()
        block_hash = header.hash_object()
        new_block_message = NewBlockEthProtocolMessage(None, block, 10)
        eth_block_info = InternalEthBlockInfo.from_new_block_msg(
            new_block_message
        )

        self.node.block_queuing_service.push(block_hash, eth_block_info)
        self.node.send_to_node_messages.clear()

        self.sut.msg_proxy_request = MagicMock()
        message = GetBlockHeadersEthProtocolMessage(None, raw_hash, 1, 0, 0)
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_not_called()
        self.assertEqual(1, len(self.node.send_to_node_messages))

        headers_sent = self.node.send_to_node_messages[0]
        self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage)
        self.assertEqual(1, len(headers_sent.get_block_headers()))
        self.assertEqual(
            block_hash, headers_sent.get_block_headers()[0].hash_object()
        )

        self.node.block_queuing_service.mark_block_seen_by_blockchain_node(
            block_hash, eth_block_info
        )
        self.sut.msg_get_block_headers(message)
        self.sut.msg_proxy_request.assert_not_called()
        self.assertEqual(2, len(self.node.send_to_node_messages))

    def test_msg_get_block_headers_block_number(self):
        block_number = 123456
        header = mock_eth_messages.get_dummy_block_header(
            12, block_number=block_number
        )
        block = mock_eth_messages.get_dummy_block(1, header)
        block_hash = header.hash_object()
        new_block_message = NewBlockEthProtocolMessage(None, block, 10)
        eth_block_info = InternalEthBlockInfo.from_new_block_msg(
            new_block_message
        )

        self.node.block_queuing_service.push(block_hash, eth_block_info)
        self.node.send_to_node_messages.clear()

        self.sut.msg_proxy_request = MagicMock()

        block_number_bytes = struct.pack(">I", block_number)
        message = GetBlockHeadersEthProtocolMessage(
            None, block_number_bytes, 1, 0, 0
        )

        self.sut.msg_get_block_headers(message)
        self.sut.msg_proxy_request.assert_not_called()
        self.assertEqual(1, len(self.node.send_to_node_messages))

        headers_sent = self.node.send_to_node_messages[0]
        self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage)
        self.assertEqual(1, len(headers_sent.get_block_headers()))
        self.assertEqual(
            block_hash, headers_sent.get_block_headers()[0].hash_object()
        )

    def test_msg_get_block_headers_known_many(self):
        self.node.opts.max_block_interval = 0

        block_hashes = []
        block_hash = None
        for i in range(20):
            block_message = InternalEthBlockInfo.from_new_block_msg(
                mock_eth_messages.new_block_eth_protocol_message(
                    i, i + 1000, block_hash
                )
            )
            block_hash = block_message.block_hash()
            block_hashes.append(block_hash)
            self.node.block_queuing_service.push(block_hash, block_message)
        self.node.block_queuing_service.best_sent_block = (1019, block_hashes[-1], 0)

        self.node.send_to_node_messages.clear()

        self.sut.msg_proxy_request = MagicMock()
        message = GetBlockHeadersEthProtocolMessage(
            None, block_hashes[10].binary, 10, 0, 0
        )
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_not_called()
        self.assertEqual(1, len(self.node.send_to_node_messages))

        headers_sent = self.node.send_to_node_messages[0]
        self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage)
        self.assertEqual(10, len(headers_sent.get_block_headers()))

        for i in range(10):
            self.assertEqual(
                block_hashes[i + 10],
                headers_sent.get_block_headers()[i].hash_object(),
            )

    def test_msg_get_block_headers_fork(self):
        self.node.opts.max_block_interval = 0

        block_hashes = []
        for i in range(20):
            block_message = InternalEthBlockInfo.from_new_block_msg(
                mock_eth_messages.new_block_eth_protocol_message(i, i + 1000)
            )
            block_hash = block_message.block_hash()
            block_hashes.append(block_hash)
            self.node.block_queuing_service.push(block_hash, block_message)

        # create fork
        block_message = InternalEthBlockInfo.from_new_block_msg(
            mock_eth_messages.new_block_eth_protocol_message(34, 1017)
        )
        block_hash = block_message.block_hash()
        block_hashes.append(block_hash)
        self.node.block_queuing_service.push(block_hash, block_message)

        self.node.send_to_node_messages.clear()

        self.sut.msg_proxy_request = MagicMock()
        message = GetBlockHeadersEthProtocolMessage(
            None, block_hashes[-1].binary, 10, 0, 1
        )
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_called_once()
        self.assertEqual(0, len(self.node.send_to_node_messages))

    def test_msg_get_block_headers_missing_block(self):
        self.node.opts.max_block_interval = 0

        block_hashes = []
        for i in (j for j in range(20) if j != 17):
            block_message = InternalEthBlockInfo.from_new_block_msg(
                mock_eth_messages.new_block_eth_protocol_message(i, i + 1000)
            )
            block_hash = block_message.block_hash()
            block_hashes.append(block_hash)
            self.node.block_queuing_service.push(block_hash, block_message)

        self.node.send_to_node_messages.clear()

        self.sut.msg_proxy_request = MagicMock()
        message = GetBlockHeadersEthProtocolMessage(
            None, block_hashes[-1].binary, 10, 0, 1
        )
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_called_once()
        self.assertEqual(0, len(self.node.send_to_node_messages))

    def test_msg_get_block_headers_future_block(self):
        self.node.opts.max_block_interval = 0

        block_hashes = []
        block_heights = []
        for i in range(20):
            block_message = InternalEthBlockInfo.from_new_block_msg(
                mock_eth_messages.new_block_eth_protocol_message(i, i + 1000)
            )
            block_hash = block_message.block_hash()
            block_heights.append(block_message.block_number())
            block_hashes.append(block_hash)
            self.node.block_queuing_service.push(block_hash, block_message)

        self.node.send_to_node_messages.clear()
        self.sut.msg_proxy_request = MagicMock()

        block_height_bytes = block_heights[-1] + 4
        block_height_bytes = block_height_bytes.to_bytes(8, "big")

        message = GetBlockHeadersEthProtocolMessage(
            None, block_height_bytes, 1, 0, 0
        )
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_not_called()
        self.assertEqual(1, len(self.node.send_to_node_messages))
        headers_sent = self.node.send_to_node_messages[0]
        self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage)
        self.assertEqual(0, len(headers_sent.get_block_headers()))

    def test_msg_block_adds_headers_and_bodies(self):
        self.node.opts.max_block_interval = 0
        self.sut.is_valid_block_timestamp = MagicMock(return_value=True)

        block_hashes = []
        block_messages = []
        block_hash = None
        for i in range(20):
            block_message = InternalEthBlockInfo.from_new_block_msg(
                mock_eth_messages.new_block_eth_protocol_message(i, i + 1000, block_hash)
            )
            block_hash = block_message.block_hash()
            block_hashes.append(block_hash)
            block_messages.append(block_message)

        # push all blocks, except for # 17
        for i in (j for j in range(20) if j != 17):
            self.node.block_queuing_service.push(
                block_hashes[i], block_messages[i]
            )
            self.node.block_queuing_service.remove_from_queue(block_hashes[i])
        self.node.block_queuing_service.best_sent_block = (1019, block_hashes[-1], 0)

        self.node.send_to_node_messages.clear()

        self.sut.msg_proxy_request = MagicMock()
        message = GetBlockHeadersEthProtocolMessage(
            None, block_hashes[10].binary, 10, 0, 0
        )
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_called_once()
        self.assertEqual(0, len(self.node.send_to_node_messages))
        self.sut.msg_proxy_request.reset_mock()

        self.sut.msg_block(block_messages[17].to_new_block_msg())
        self.sut.msg_get_block_headers(message)

        self.sut.msg_proxy_request.assert_not_called()
        self.assertEqual(1, len(self.node.send_to_node_messages))

        headers_sent = self.node.send_to_node_messages[0]
        self.assertIsInstance(headers_sent, BlockHeadersEthProtocolMessage)
        self.assertEqual(10, len(headers_sent.get_block_headers()))

        for i in range(10):
            self.assertEqual(
                block_hashes[i + 10],
                headers_sent.get_block_headers()[i].hash_object(),
            )

    def test_msg_tx(self):
        self.node.feed_manager.publish_to_feed = MagicMock()
        self.node.opts.ws = True

        transaction = mock_eth_messages.get_dummy_transaction(1)
        transaction_hash = transaction.hash()
        transaction_contents = transaction.contents()
        messages = TransactionsEthProtocolMessage(None, [transaction])

        self.sut.msg_tx(messages)

        # published to both feeds
        self.node.feed_manager.publish_to_feed.assert_has_calls(
            [
                call(
                    EthNewTransactionFeed.NAME,
                    EthRawTransaction(transaction_hash, transaction_contents)
                ),
                call(
                    EthPendingTransactionFeed.NAME,
                    EthRawTransaction(transaction_hash, transaction_contents)
                ),
            ]
        )

        # broadcast transactions
        self.assertEqual(1, len(self.node.broadcast_messages))