Repository: rotki/rotki

Issue:
Querying trades of a single exchange that is ignored for sync should not pull data
## Problem Definition

rotki has a setting for each CEX that is meant to ignore syncing data with that exchange. The problem is that in the trades querying view, the frontend asks trades for each exchange separately.

In our code the function that decides whether data needs to be pulled from the exchange respects the setting when it concerns all exchanges (it iterates only connected exchanges that are not ignored), but not when querying a single one directly.

https://github.com/rotki/rotki/blob/62540a0e094b4bf9f5be87876b84dae99dc31300/rotkehlchen/history/events.py#L125-L127

This needs to change.

### System Description
Operating system: Any 
Rotki version: 1.26.2

## Task

- [ ] Make it so that the ignore_sync setting is respected when querying a single exchange.
- [ ] Write a test for it
- [ ] See if there is any other places we should respec this setting and we are not doing so.


Discussion:
Hello, I'm interested in contributing to rotki.

Can I try to fix this?

At first glance, I think we can change all instances of `ExchangeManager.connected_exchanges.get(location)` to use `ExchangeManager.iterate_exchanges(location)` wherever the ignore_sync setting is to be respected.
> Hello, I'm interested in contributing to rotki.
> 
> Can I try to fix this?
> 
> At first glance, I think we can change all instances of `ExchangeManager.connected_exchanges.get(location)` to use `ExchangeManager.iterate_exchanges(location)` wherever the ignore_sync setting is to be respected.

Hello @crunchy-roll! Thanks for your interest in rotki. It would make sense for you to join our discord to chat with the dev team too: https://discord.gg/aGCxHG7

Need to have DMs open as there is a captcha bot.

As for the solution here, no iterating always is not the solution. The approach here is to take into account the setting in the places where it's not. Which I think (would need to check the code to be sure) is one.
Ah sorry, I didn't explain well enough. Will continue in discord :)

Resolving patch:
diff --git a/rotkehlchen/history/events.py b/rotkehlchen/history/events.py
--- a/rotkehlchen/history/events.py
+++ b/rotkehlchen/history/events.py
@@ -122,7 +122,13 @@ def _query_services_for_trades(self, filter_query: TradesFilterQuery) -> None:
         from_ts = filter_query.from_ts
         to_ts = filter_query.to_ts
 
-        if location is not None:
+        with self.db.conn.read_ctx() as cursor:
+            excluded_locations = {exchange.location for exchange in self.db.get_settings(cursor).non_syncing_exchanges}  # noqa: E501
+
+        if (
+            location is not None and
+            location not in excluded_locations
+        ):
             self.query_location_latest_trades(location=location, from_ts=from_ts, to_ts=to_ts)
             return
 


Test patch:
diff --git a/rotkehlchen/tests/api/test_exchanges.py b/rotkehlchen/tests/api/test_exchanges.py
--- a/rotkehlchen/tests/api/test_exchanges.py
+++ b/rotkehlchen/tests/api/test_exchanges.py
@@ -1,6 +1,7 @@
 import os
 import random
 from http import HTTPStatus
+from typing import TYPE_CHECKING
 from unittest.mock import patch
 from urllib.parse import urlencode
 
@@ -12,6 +13,7 @@
 from rotkehlchen.constants.limits import FREE_ASSET_MOVEMENTS_LIMIT, FREE_TRADES_LIMIT
 from rotkehlchen.db.constants import KRAKEN_ACCOUNT_TYPE_KEY
 from rotkehlchen.db.filtering import AssetMovementsFilterQuery, TradesFilterQuery
+from rotkehlchen.db.settings import ModifiableDBSettings
 from rotkehlchen.exchanges.bitfinex import API_KEY_ERROR_MESSAGE as BITFINEX_API_KEY_ERROR_MESSAGE
 from rotkehlchen.exchanges.bitstamp import (
     API_KEY_ERROR_CODE_ACTION as BITSTAMP_API_KEY_ERROR_CODE_ACTION,
@@ -51,6 +53,10 @@
 from rotkehlchen.tests.utils.mock import MockResponse
 from rotkehlchen.types import AssetMovementCategory, Location, Timestamp, TradeType
 
+if TYPE_CHECKING:
+    from requests import Response
+    from rotkehlchen.api.server import APIServer
+
 
 def mock_validate_api_key():
     raise ValueError('BOOM ERROR!')
@@ -577,7 +583,7 @@ def test_exchange_query_balances_errors(rotkehlchen_api_server_with_exchanges):
 
 @pytest.mark.parametrize('number_of_eth_accounts', [0])
 @pytest.mark.parametrize('added_exchanges', [(Location.BINANCE, Location.POLONIEX)])
-def test_exchange_query_trades(rotkehlchen_api_server_with_exchanges):
+def test_exchange_query_trades(rotkehlchen_api_server_with_exchanges: 'APIServer'):
     """Test that using the exchange trades query endpoint works fine"""
     async_query = random.choice([False, True])
     server = rotkehlchen_api_server_with_exchanges
@@ -617,7 +623,7 @@ def test_exchange_query_trades(rotkehlchen_api_server_with_exchanges):
     assert_binance_trades_result([x['entry'] for x in trades if x['entry']['location'] == 'binance'])  # noqa: E501
     assert_poloniex_trades_result([x['entry'] for x in trades if x['entry']['location'] == 'poloniex'])  # noqa: E501
 
-    def assert_okay(response):
+    def assert_okay(response: 'Response'):
         """Helper function for DRY checking below assertions"""
         if async_query:
             task_id = assert_ok_async_response(response)
@@ -643,6 +649,34 @@ def assert_okay(response):
             api_url_for(server, "tradesresource") + '?' + urlencode(data))
         assert_okay(response)
 
+    # check that for poloniex we have information
+    with setup.binance_patch, setup.polo_patch:
+        response = requests.get(
+            url=api_url_for(server, "tradesresource"),
+            json={'location': 'poloniex'},
+        )
+
+    result = assert_proper_response_with_result(response)
+    assert len(result['entries']) != 0
+
+    # exclude poloniex as location and delete the trades from there
+    db = server.rest_api.rotkehlchen.data.db
+    poloniex_exchange = server.rest_api.rotkehlchen.exchange_manager.connected_exchanges[Location.POLONIEX][0]  # noqa: E501
+    with db.user_write() as cursor:
+        db.set_settings(cursor, ModifiableDBSettings(
+            non_syncing_exchanges=[poloniex_exchange.location_id()],
+        ))
+        db.purge_exchange_data(cursor, Location.POLONIEX)
+
+    # query the API and check that information is not queried again
+    with setup.binance_patch, setup.polo_patch:
+        response = requests.get(
+            url=api_url_for(server, "tradesresource"),
+            json={'location': 'poloniex'},
+        )
+    result = assert_proper_response_with_result(response)
+    assert len(result['entries']) == 0
+
 
 @pytest.mark.parametrize('number_of_eth_accounts', [0])
 @pytest.mark.parametrize('added_exchanges', [(Location.KRAKEN, Location.POLONIEX)])
diff --git a/rotkehlchen/tests/unit/test_tasks_manager.py b/rotkehlchen/tests/unit/test_tasks_manager.py
--- a/rotkehlchen/tests/unit/test_tasks_manager.py
+++ b/rotkehlchen/tests/unit/test_tasks_manager.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import TYPE_CHECKING, List
 from unittest.mock import MagicMock, patch
 
 import gevent
@@ -7,6 +7,7 @@
 from rotkehlchen.chain.bitcoin.hdkey import HDKey
 from rotkehlchen.chain.bitcoin.xpub import XpubData
 from rotkehlchen.db.ethtx import DBEthTx
+from rotkehlchen.db.settings import ModifiableDBSettings
 from rotkehlchen.errors.misc import RemoteError
 from rotkehlchen.premium.premium import Premium, PremiumCredentials, SubscriptionStatus
 from rotkehlchen.tasks.manager import PREMIUM_STATUS_CHECK, TaskManager
@@ -16,6 +17,10 @@
 from rotkehlchen.utils.hexbytes import hexstring_to_bytes
 from rotkehlchen.utils.misc import ts_now
 
+if TYPE_CHECKING:
+    from rotkehlchen.exchanges.exchange import ExchangeInterface
+    from rotkehlchen.exchanges.manager import ExchangeManager
+
 
 class MockPremiumSyncManager():
 
@@ -168,6 +173,21 @@ def mock_query_history(start_ts, end_ts, only_cache):
         raise AssertionError(f'exchange query was not scheduled within {timeout} seconds') from e  # noqa: E501
 
 
+def test_maybe_schedule_exchange_query_ignore_exchanges(
+        task_manager: 'TaskManager',
+        exchange_manager: 'ExchangeManager',
+        poloniex: 'ExchangeInterface',
+) -> None:
+    """Verify that task manager respects the ignored exchanges when querying trades"""
+    exchange_manager.connected_exchanges[Location.POLONIEX] = [poloniex]
+    task_manager.exchange_manager = exchange_manager
+    with task_manager.database.user_write() as cursor:
+        task_manager.database.set_settings(cursor, ModifiableDBSettings(
+            non_syncing_exchanges=[poloniex.location_id()],
+        ))
+    assert task_manager._maybe_schedule_exchange_history_query() is None
+
+
 @pytest.mark.parametrize('one_receipt_in_db', [True, False])
 def test_maybe_schedule_ethereum_txreceipts(
         task_manager,