Repository: rotki/rotki

Issue:
Cleanup v2 spam asset update and specify min version for consumption of data updates
## Problem Definition

This issue is to describe two problems that are tied together.

@yabirgb ended up duplicating all the spam asset data that had the wrong chain name "matic" in https://raw.githubusercontent.com/rotki/data/develop/updates/spam_assets/v2.json because if users downloaded the spam update before 1.29 they would need the name matic. After they would need the name "polygon_pos".

This is showing a problem of these willy nilly data updates that can happen whenever data needs to have different forms across updates due to the app having different logic.

## Task

- We need to remove spam asset chain "matic" from v2 spam asset update. Just remove all duplicates and keep only the polygon pos name.
- Create logic in the app that checks for each data update if there is a minimum rotki version that needs to consume it. And don't consume it if version is lower. We already do this but with DB version, for assets using the info.json: https://github.com/rotki/assets/blob/develop/updates/info.json. Updates also have an info.json. So we should use that: https://github.com/rotki/data/blob/develop/updates/info.json. Such logic will solve many problems we have had and may have again in the future about fearing how an update will be seen by users who have not updated. Simpy **do not let them consume it**. They will when they update.


Discussion:

Resolving patch:
diff --git a/rotkehlchen/assets/spam_assets.py b/rotkehlchen/assets/spam_assets.py
--- a/rotkehlchen/assets/spam_assets.py
+++ b/rotkehlchen/assets/spam_assets.py
@@ -79,12 +79,18 @@ def update_spam_assets(db: 'DBHandler', assets_info: list[dict[str, Any]]) -> in
     # and in that case query_token_spam_list add it to the global DB
     with db.conn.read_ctx() as cursor:
         ignored_asset_ids = db.get_ignored_asset_ids(cursor)
-    assets_added = 0
+
+    write_data = []
     for token in spam_tokens:
         if token.identifier in ignored_asset_ids:
             continue
 
-        with db.user_write() as write_cursor:
-            db.add_to_ignored_assets(write_cursor=write_cursor, asset=token)
-        assets_added += 1
+        write_data.append(('ignored_asset', token.identifier))
+
+    with db.user_write() as write_cursor:
+        write_cursor.executemany(
+            'INSERT OR IGNORE INTO multisettings(name, value) VALUES(?, ?)', write_data,
+        )
+        assets_added = write_cursor.rowcount
+
     return assets_added
diff --git a/rotkehlchen/db/dbhandler.py b/rotkehlchen/db/dbhandler.py
--- a/rotkehlchen/db/dbhandler.py
+++ b/rotkehlchen/db/dbhandler.py
@@ -2991,7 +2991,7 @@ def get_rpc_nodes(
             self,
             blockchain: SupportedBlockchain,
             only_active: bool = False,
-    ) -> list[WeightedNode]:
+    ) -> Sequence[WeightedNode]:
         """
         Get all the nodes in the database. If only_active is set to true only the nodes that
         have the column active set to True will be returned.
diff --git a/rotkehlchen/db/updates.py b/rotkehlchen/db/updates.py
--- a/rotkehlchen/db/updates.py
+++ b/rotkehlchen/db/updates.py
@@ -1,10 +1,11 @@
 import json
 import logging
 from collections.abc import Sequence
-from typing import TYPE_CHECKING, Any, Literal, Union, overload
+from typing import TYPE_CHECKING, Any
 
 import requests
 
+from packaging import version as pversion
 from rotkehlchen.assets.spam_assets import update_spam_assets
 from rotkehlchen.db.addressbook import DBAddressbook
 from rotkehlchen.db.settings import CachedSettings
@@ -22,6 +23,7 @@
 )
 from rotkehlchen.utils.misc import is_production, ts_now
 from rotkehlchen.utils.network import query_file
+from rotkehlchen.utils.version_check import get_current_version
 
 if TYPE_CHECKING:
     from rotkehlchen.db.dbhandler import DBCursor, DBHandler
@@ -33,12 +35,6 @@
 log = RotkehlchenLogsAdapter(logger)
 
 
-SPAM_ASSETS_URL = 'https://raw.githubusercontent.com/rotki/data/{branch}/updates/spam_assets/v{version}.json'  # noqa: E501
-RPC_NODES_URL = 'https://raw.githubusercontent.com/rotki/data/{branch}/updates/rpc_nodes/v{version}.json'  # noqa: E501
-CONTRACTS_URL = 'https://raw.githubusercontent.com/rotki/data/{branch}/updates/contracts/v{version}.json'  # noqa: E501
-GLOBAL_ADDRESSBOOK_URL = 'https://raw.githubusercontent.com/rotki/data/{branch}/updates/global_addressbook/v{version}.json'  # noqa: E501
-
-
 class RotkiDataUpdater:
     """
     Handle updates from the rotki repository related to data that needs to be provided to
@@ -59,7 +55,56 @@ def __init__(self, msg_aggregator: 'MessagesAggregator', user_db: 'DBHandler') -
             UpdateType.RPC_NODES: self.update_rpc_nodes,
             UpdateType.CONTRACTS: self.update_contracts,
             UpdateType.GLOBAL_ADDRESSBOOK: self.update_global_addressbook,
-        }
+        }  # If we ever change this also change tests/unit/test_data_updates::reset_update_type_mappings  # noqa: E501
+        self.version = pversion.parse(get_current_version().our_version)
+
+    def update_single(
+            self,
+            update_type: UpdateType,
+            from_version: int,
+            to_version: int,
+            limits: dict[str, dict],
+    ) -> None:
+        """Common code to update a single type of data
+
+        Check the updates in the inclusive range [from_version + 1, to_version] and apply
+        the needed updates if any. Also save latest applied update type in the DB.
+        """
+        for update_version in range(from_version + 1, to_version + 1):
+            version_info = limits.get(str(update_version), {})  # json treats keys as string
+            min_version = version_info.get('min_version', None)
+            if min_version is not None:
+                p_min_version = pversion.parse(min_version)
+                if p_min_version > self.version:
+                    log.warning(f'Not updating {update_type.value} to {update_version=} due to {min_version=}')  # noqa: E501
+                    break  # stop iteration since all next versions will also hit this. User needs to update  # noqa: E501
+
+            max_version = version_info.get('max_version', None)
+            if max_version is not None:
+                p_max_version = pversion.parse(max_version)
+                if p_max_version < self.version:
+                    log.warning(f'Not updating {update_type.value} to {update_version=} due to {max_version=}')  # noqa: E501
+                    continue  # probably can never apply this update. Check next one
+
+            file_url = f'https://raw.githubusercontent.com/rotki/data/{self.branch}/updates/{update_type.value}/v{update_version}.json'  # noqa: E501
+            try:
+                updates = query_file(file_url, True)
+            except RemoteError as e:
+                log.warning(f'Failed to update {update_type.value} due to {e!s}')
+                continue  # perhaps broken file? Skipping
+
+            updated_data = updates.get(update_type.value)
+            if updated_data is None:
+                log.error(f'Remote update {file_url} does not contain {update_type.value} key')
+                continue  # perhaps broken file? Skipping
+
+            # At this point we can apply the update. Type ignore is due to different sigs of data
+            self.update_type_mappings[update_type](updated_data, update_version)  # type: ignore
+            with self.user_db.conn.write_ctx() as write_cursor:
+                write_cursor.execute(  # this was the last update to be applied for this data type, so remember it  # noqa: E501
+                    'INSERT OR REPLACE INTO settings(name, value) VALUES (?, ?)',
+                    (update_type.serialize(), update_version),
+                )
 
     def _get_remote_info_json(self) -> dict[str, Any]:
         """Retrieve remote file with information for different updates
@@ -81,45 +126,20 @@ def _get_remote_info_json(self) -> dict[str, Any]:
 
         return json_data
 
-    def update_spam_assets(self, from_version: int, to_version: int) -> None:
+    def update_spam_assets(self, data: list[dict[str, Any]], version: int) -> None:
         """
-        Check the updates in the inclusive range [from_version + 1, to_version] and update the
-        spam assets for those versions. Assets are also added to the globaldb if they don't exist
+        Spam assets update code. Assets are also added to the globaldb if they don't exist
         locally
         """
-        for version in range(from_version + 1, to_version + 1):
-            success, updated_assets = self._get_file_content(
-                file_url=SPAM_ASSETS_URL.format(branch=self.branch, version=version),
-                update_type=UpdateType.SPAM_ASSETS,
-            )
-            if success is False:
-                continue
-
-            log.info(f'Applying update for spam assets from v{version}. {len(updated_assets)} tokens to add')  # noqa: E501
-            with GlobalDBHandler().conn.critical_section():
-                # Use a critical section to avoid another greenlet adding spam assets at
-                # the same time
-                update_spam_assets(db=self.user_db, assets_info=updated_assets)
-
-            with self.user_db.conn.write_ctx() as write_cursor:
-                write_cursor.execute(
-                    'INSERT OR REPLACE INTO settings(name, value) VALUES (?, ?)',
-                    (UpdateType.SPAM_ASSETS.serialize(), version),
-                )
-
-    def update_rpc_nodes(self, from_version: int, to_version: int) -> None:  # pylint: disable=unused-argument  # noqa: E501
-        """Checks the update for the latest version and update the default rpc nodes in the global db.
-
-        It also updates the user db with these default nodes.
-        """  # noqa: E501
-        success, updated_rpc_nodes = self._get_file_content(
-            file_url=RPC_NODES_URL.format(branch=self.branch, version=to_version),
-            update_type=UpdateType.RPC_NODES,
-        )
-        if success is False:
-            return
-
-        log.info(f'Applying update for rpc nodes from v{to_version}')
+        log.info(f'Applying update for spam assets to v{version}. {len(data)} tokens to add')  # noqa: E501
+        with GlobalDBHandler().conn.critical_section():
+            # Use a critical section to avoid another greenlet adding spam assets at
+            # the same time
+            update_spam_assets(db=self.user_db, assets_info=data)
+
+    def update_rpc_nodes(self, data: list[dict[str, Any]], version: int) -> None:
+        """RPC nodes update code. It also updates the user db with these default nodes."""
+        log.info(f'Applying update for rpc nodes to v{version}')
         new_default_nodes: list[tuple[Any, ...]] = [
             (
                 node['name'],
@@ -129,7 +149,7 @@ def update_rpc_nodes(self, from_version: int, to_version: int) -> None:  # pylin
                 str(FVal(node['weight'])),
                 node['blockchain'],
             )
-            for node in updated_rpc_nodes
+            for node in data
         ]
         with GlobalDBHandler().conn.write_ctx() as write_cursor:
             # Update the default nodes, stored in the global DB
@@ -141,131 +161,102 @@ def update_rpc_nodes(self, from_version: int, to_version: int) -> None:  # pylin
                 new_default_nodes,
             )
 
-        with self.user_db.conn.write_ctx() as write_cursor:
-            write_cursor.execute(
-                'INSERT OR REPLACE INTO settings(name, value) VALUES (?, ?)',
-                (UpdateType.RPC_NODES.serialize(), to_version),
-            )
-
         self._update_user_nodes(
             existing_default_nodes=existing_default_nodes,
             new_default_nodes=new_default_nodes,
         )
 
-    def update_contracts(self, from_version: int, to_version: int) -> None:
+    def update_contracts(self, data: dict[str, Any], version: int) -> None:
         """
         Updates evm contracts and ABIs in globaldb.
         If an ABI already exists in the db, it is reused.
         If a contract (address + chain id) exists, it is replaced by the remote counterpart.
         """
-        for version in range(from_version + 1, to_version + 1):
-            success, updated_contracts = self._get_file_content(
-                file_url=CONTRACTS_URL.format(branch=self.branch, version=version),
-                update_type=UpdateType.CONTRACTS,
+        log.info(f'Applying update for contracts to v{version}')
+        remote_id_to_local_id = {}
+        for single_abi_data in data['abis_data']:
+            # store a mapping of the virtual id to the id in the user's globaldb
+            abi_id = GlobalDBHandler().get_or_write_abi(
+                serialized_abi=single_abi_data['value'],
+                abi_name=single_abi_data.get('name'),
             )
-            if success is False:
-                return
-
-            log.info(f'Applying update for contracts to v{version}')
-            remote_id_to_local_id = {}
-            for single_abi_data in updated_contracts['abis_data']:
-                # store a mapping of the virtual id to the id in the user's globaldb
-                abi_id = GlobalDBHandler().get_or_write_abi(
-                    serialized_abi=single_abi_data['value'],
-                    abi_name=single_abi_data.get('name'),
-                )
-                remote_id_to_local_id[single_abi_data['id']] = abi_id
-
-            new_contracts_data = []
-            for single_contract_data in updated_contracts['contracts_data']:
-                if single_contract_data['abi'] not in remote_id_to_local_id:
-                    self.msg_aggregator.add_error(
-                        f'ABI with id {single_contract_data["abi"]} was missing in a contracts '
-                        f'update. Please report it to the rotki team.',
-                    )
-                    continue
-                new_contracts_data.append((
-                    single_contract_data['address'],
-                    single_contract_data['chain_id'],
-                    remote_id_to_local_id[single_contract_data['abi']],
-                    single_contract_data['deployed_block'],
-                ))
-
-            with GlobalDBHandler().conn.write_ctx() as write_cursor:
-                write_cursor.executemany(
-                    'INSERT OR REPLACE INTO contract_data(address, chain_id, abi, deployed_block) VALUES(?, ?, ?, ?)',  # noqa: E501
-                    new_contracts_data,
+            remote_id_to_local_id[single_abi_data['id']] = abi_id
+
+        new_contracts_data = []
+        for single_contract_data in data['contracts_data']:
+            if single_contract_data['abi'] not in remote_id_to_local_id:
+                self.msg_aggregator.add_error(
+                    f'ABI with id {single_contract_data["abi"]} was missing in a contracts '
+                    f'update. Please report it to the rotki team.',
                 )
+                continue
+            new_contracts_data.append((
+                single_contract_data['address'],
+                single_contract_data['chain_id'],
+                remote_id_to_local_id[single_contract_data['abi']],
+                single_contract_data['deployed_block'],
+            ))
 
-            with self.user_db.conn.write_ctx() as write_cursor:
-                write_cursor.execute(
-                    'INSERT OR REPLACE INTO settings(name, value) VALUES (?, ?)',
-                    (UpdateType.CONTRACTS.serialize(), version),
-                )
+        with GlobalDBHandler().conn.write_ctx() as write_cursor:
+            write_cursor.executemany(
+                'INSERT OR REPLACE INTO contract_data(address, chain_id, abi, deployed_block) VALUES(?, ?, ?, ?)',  # noqa: E501
+                new_contracts_data,
+            )
 
-    def update_global_addressbook(self, from_version: int, to_version: int) -> None:
-        """Downloads and applies global addressbook updates from from_version to to_version"""
+    def update_global_addressbook(self, data: list[dict[str, Any]], version: int) -> None:
+        """Applies global addressbook updates"""
+        log.info(f'Applying update for addressbook to v{version}')
         db_addressbook = DBAddressbook(self.user_db)
-        for version in range(from_version + 1, to_version + 1):
-            log.debug(f'Applying global address book update version {version}')
-            success, new_raw_entries = self._get_file_content(
-                file_url=GLOBAL_ADDRESSBOOK_URL.format(branch=self.branch, version=version),
-                update_type=UpdateType.GLOBAL_ADDRESSBOOK,
-            )
-            if success is False:
-                log.error(f'Failed to download globaldb addressbook for version {version}')
+        entries_to_add = []
+        for raw_entry in data:
+            try:
+                address = deserialize_evm_address(raw_entry['address'])
+                blockchain = SupportedBlockchain.deserialize(raw_entry['blockchain']) if raw_entry['blockchain'] else None  # noqa: E501
+            except DeserializationError as e:
+                self.msg_aggregator.add_error(
+                    f'Could not deserialize address {raw_entry["address"]} or blockchain '
+                    f'{raw_entry["blockchain"]} that was seen in a global addressbook update. '
+                    f'Please report it to the rotki team. {e!s}',
+                )
                 continue
 
-            entries_to_add = []
-            for raw_entry in new_raw_entries:
-                try:
-                    address = deserialize_evm_address(raw_entry['address'])
-                    blockchain = SupportedBlockchain.deserialize(raw_entry['blockchain']) if raw_entry['blockchain'] else None  # noqa: E501
-                except DeserializationError as e:
-                    self.msg_aggregator.add_error(
-                        f'Could not deserialize address {raw_entry["address"]} or blockchain '
-                        f'{raw_entry["blockchain"]} that was seen in a global addressbook update. '
-                        f'Please report it to the rotki team. {e!s}',
-                    )
-                    continue
-
-                entry = AddressbookEntry(
+            entry = AddressbookEntry(
+                address=address,
+                name=raw_entry['new_name'],
+                blockchain=blockchain,
+            )
+            existing_name = db_addressbook.get_addressbook_entry_name(
+                book_type=AddressbookType.GLOBAL,
+                chain_address=OptionalChainAddress(
                     address=address,
-                    name=raw_entry['new_name'],
                     blockchain=blockchain,
-                )
-                existing_name = db_addressbook.get_addressbook_entry_name(
+                ),
+            )
+            if existing_name is None:
+                entries_to_add.append(entry)
+            elif raw_entry.get('old_name') is not None and existing_name == raw_entry['old_name']:  # noqa: E501
+                # If old_name is specified, replace the entry with the old name.
+                db_addressbook.delete_addressbook_entries(
                     book_type=AddressbookType.GLOBAL,
-                    chain_address=OptionalChainAddress(
+                    chain_addresses=[OptionalChainAddress(
                         address=address,
                         blockchain=blockchain,
-                    ),
+                    )],
                 )
-                if existing_name is None:
-                    entries_to_add.append(entry)
-                elif raw_entry.get('old_name') is not None and existing_name == raw_entry['old_name']:  # noqa: E501
-                    # If old_name is specified, replace the entry with the old name.
-                    db_addressbook.delete_addressbook_entries(
-                        book_type=AddressbookType.GLOBAL,
-                        chain_addresses=[OptionalChainAddress(
-                            address=address,
-                            blockchain=blockchain,
-                        )],
-                    )
-                    entries_to_add.append(entry)
-                else:
-                    log.debug(
-                        f'Global address book entry for address {address} and blockchain '
-                        f'{blockchain} already exists (probably was input by the user). '
-                        f'Update for this entry will not be applied.',
-                    )
-                    continue
-
-            with GlobalDBHandler().conn.write_ctx() as write_cursor:
-                db_addressbook.add_addressbook_entries(
-                    write_cursor=write_cursor,
-                    entries=entries_to_add,
+                entries_to_add.append(entry)
+            else:
+                log.debug(
+                    f'Global address book entry for address {address} and blockchain '
+                    f'{blockchain} already exists (probably was input by the user). '
+                    f'Update for this entry will not be applied.',
                 )
+                continue
+
+        with GlobalDBHandler().conn.write_ctx() as write_cursor:
+            db_addressbook.add_addressbook_entries(
+                write_cursor=write_cursor,
+                entries=entries_to_add,
+            )
 
     def check_for_updates(self, updates: Sequence[UpdateType] = tuple(UpdateType)) -> None:
         """Retrieve the information about the latest available update"""
@@ -276,6 +267,14 @@ def check_for_updates(self, updates: Sequence[UpdateType] = tuple(UpdateType)) -
             # skip updates, but write last data update to not spam periodic tasks
         else:
             for update_type in updates:
+                try:
+                    info = remote_information[update_type.value]
+                    latest_version = info['latest']
+                except KeyError as e:
+                    log.error(f'Could not find key {e} in remote update info file for {update_type.value}')  # noqa: E501
+                    continue
+                limits = info.get('limits', {})
+
                 # Get latest applied version
                 with self.user_db.conn.read_ctx() as cursor:
                     local_version = self._check_for_last_version(
@@ -284,9 +283,13 @@ def check_for_updates(self, updates: Sequence[UpdateType] = tuple(UpdateType)) -
                     )
 
                 # Update all remote data
-                latest_version = remote_information[update_type.value]['latest']
                 if local_version < latest_version:
-                    self.update_type_mappings[update_type](from_version=local_version, to_version=latest_version)  # noqa: E501
+                    self.update_single(
+                        update_type=update_type,
+                        from_version=local_version,
+                        to_version=latest_version,
+                        limits=limits,
+                    )
 
         with self.user_db.user_write() as cursor:
             cursor.execute(  # remember last time data updates were detected
@@ -303,48 +306,6 @@ def _check_for_last_version(cursor: 'DBCursor', update_type: UpdateType) -> int:
         ).fetchone()
         return int(found_version[0]) if found_version is not None else 0
 
-    @staticmethod
-    @overload
-    def _get_file_content(
-            file_url: str,
-            update_type: Literal[
-                UpdateType.SPAM_ASSETS,
-                UpdateType.RPC_NODES,
-                UpdateType.GLOBAL_ADDRESSBOOK,
-            ],
-    ) -> tuple[bool, list[dict[str, Any]]]:
-        ...
-
-    @staticmethod
-    @overload
-    def _get_file_content(
-            file_url: str,
-            update_type: Literal[UpdateType.CONTRACTS],
-    ) -> tuple[bool, dict[str, Any]]:
-        ...
-
-    @staticmethod
-    def _get_file_content(
-            file_url: str,
-            update_type: UpdateType,
-    ) -> tuple[bool, Union[list[dict[str, Any]], dict[str, Any]]]:
-        """Retrieves the content of data to be updated.
-
-        Returns True/False for success along with the content.
-        """
-        try:
-            updates = query_file(file_url, is_json=True)
-        except RemoteError as e:
-            log.warning(f'Failed to update {update_type.value} due to {e!s}')
-            return False, []
-
-        update_content = updates.get(update_type.value)
-        if update_content is None:
-            log.error(f'Remote update {file_url} does not contain {update_type.value} key')
-            return False, []
-
-        return True, update_content
-
     def _update_user_nodes(
             self,
             existing_default_nodes: list[tuple[Any, ...]],


Test patch:
diff --git a/rotkehlchen/tests/api/test_misc.py b/rotkehlchen/tests/api/test_misc.py
--- a/rotkehlchen/tests/api/test_misc.py
+++ b/rotkehlchen/tests/api/test_misc.py
@@ -158,6 +158,7 @@ def patched_get_latest_release(_klass):
     )
 
 
+@pytest.mark.parametrize('ethereum_manager_connect_at_start', ['DEFAULT'])
 def test_manage_nodes(rotkehlchen_api_server):
     """Test that list of nodes can be correctly updated and queried"""
     database = rotkehlchen_api_server.rest_api.rotkehlchen.data.db
diff --git a/rotkehlchen/tests/api/test_sushiswap.py b/rotkehlchen/tests/api/test_sushiswap.py
--- a/rotkehlchen/tests/api/test_sushiswap.py
+++ b/rotkehlchen/tests/api/test_sushiswap.py
@@ -17,10 +17,7 @@
     assert_proper_response_with_result,
     wait_for_async_task,
 )
-from rotkehlchen.tests.utils.ethereum import (
-    ETHEREUM_NODES_PARAMETERS_WITH_PRUNED_AND_NOT_ARCHIVED,
-    get_decoded_events_of_transaction,
-)
+from rotkehlchen.tests.utils.ethereum import INFURA_ETH_NODE, get_decoded_events_of_transaction
 from rotkehlchen.types import ChecksumEvmAddress, deserialize_evm_tx_hash
 
 SWAP_ADDRESS = string_to_evm_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121')
@@ -44,7 +41,7 @@ def test_get_balances_module_not_activated(rotkehlchen_api_server):
 @pytest.mark.parametrize('ethereum_accounts', [['0x31089Ef6f99FB83F95178DBBf08A7A4Bf2eC9fd2']])
 @pytest.mark.parametrize('ethereum_modules', [['sushiswap']])
 @pytest.mark.parametrize('network_mocking', [False])
-@pytest.mark.parametrize(*ETHEREUM_NODES_PARAMETERS_WITH_PRUNED_AND_NOT_ARCHIVED)
+@pytest.mark.parametrize('ethereum_manager_connect_at_start', [(INFURA_ETH_NODE,)])
 def test_get_balances(
         rotkehlchen_api_server: APIServer,
         start_with_valid_premium: bool,
diff --git a/rotkehlchen/tests/data_migrations/test_migrations.py b/rotkehlchen/tests/data_migrations/test_migrations.py
--- a/rotkehlchen/tests/data_migrations/test_migrations.py
+++ b/rotkehlchen/tests/data_migrations/test_migrations.py
@@ -14,6 +14,7 @@
     DataMigrationManager,
     MigrationRecord,
 )
+from rotkehlchen.db.constants import UpdateType
 from rotkehlchen.db.dbhandler import DBHandler
 from rotkehlchen.fval import FVal
 from rotkehlchen.globaldb.handler import GlobalDBHandler
@@ -359,11 +360,15 @@ def test_migration_10(
         assert write_cursor.execute('SELECT COUNT(*) FROM rpc_nodes WHERE name="polygon pos etherscan"').fetchone()[0] == 1  # noqa: E501
 
 
-@pytest.mark.vcr()
+@pytest.mark.vcr(filter_query_parameters=['apikey'])
 @pytest.mark.parametrize('data_migration_version', [10])
 @pytest.mark.parametrize('perform_upgrades_at_unlock', [True])
-@pytest.mark.parametrize('ethereum_accounts', [[make_evm_address(), make_evm_address()]])
+@pytest.mark.parametrize('ethereum_accounts', [[
+    '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84',  # mainnet contract
+    '0x280fc92644Dd4b9f5Bb5be652B4849611e8AC9Dd',  # mainnet + arbitrum activity only (test time)
+]])
 @pytest.mark.parametrize('legacy_messages_via_websockets', [True])
+@pytest.mark.parametrize('network_mocking', [False])
 def test_migration_11(
         rotkehlchen_api_server: 'APIServer',
         ethereum_accounts: list[ChecksumEvmAddress],
@@ -375,25 +380,24 @@ def test_migration_11(
     - Test that detecting arbitrum one accounts works properly
     """
     rotki = rotkehlchen_api_server.rest_api.rotkehlchen
-
+    # set high version so that DB data updates dont't get applied
+    with rotki.data.db.conn.write_ctx() as write_cursor:
+        write_cursor.executemany(
+            'INSERT OR REPLACE INTO settings(name, value) VALUES (?, ?)',
+            [
+                (UpdateType.SPAM_ASSETS.serialize(), 999),
+                (UpdateType.RPC_NODES.serialize(), 999),
+                (UpdateType.CONTRACTS.serialize(), 999),
+                (UpdateType.GLOBAL_ADDRESSBOOK.serialize(), 999),
+            ],
+        )
     migration_patch = patch(
         'rotkehlchen.data_migrations.manager.MIGRATION_LIST',
         new=[MIGRATION_LIST[5]],
     )
 
     arbitrum_one_addresses = [ethereum_accounts[1]]
-    with ExitStack() as stack:
-        setup_evm_addresses_activity_mock(
-            stack=stack,
-            chains_aggregator=rotki.chains_aggregator,
-            eth_contract_addresses=[ethereum_accounts[0]],
-            ethereum_addresses=[],
-            avalanche_addresses=[],
-            optimism_addresses=[],
-            polygon_pos_addresses=[],
-            arbitrum_one_addresses=arbitrum_one_addresses,
-        )
-        stack.enter_context(migration_patch)
+    with migration_patch:
         DataMigrationManager(rotki).maybe_migrate_data()
 
     # check that detecting arbitrum one accounts works properly
diff --git a/rotkehlchen/tests/fixtures/blockchain.py b/rotkehlchen/tests/fixtures/blockchain.py
--- a/rotkehlchen/tests/fixtures/blockchain.py
+++ b/rotkehlchen/tests/fixtures/blockchain.py
@@ -30,6 +30,7 @@
 from rotkehlchen.externalapis.covalent import Covalent
 from rotkehlchen.externalapis.opensea import Opensea
 from rotkehlchen.premium.premium import Premium
+from rotkehlchen.tests.utils.blockchain import maybe_modify_rpc_nodes
 from rotkehlchen.tests.utils.decoders import patch_decoder_reload_data
 from rotkehlchen.tests.utils.ethereum import wait_until_all_nodes_connected
 from rotkehlchen.tests.utils.evm import maybe_mock_evm_inquirer
@@ -64,18 +65,7 @@ def _initialize_and_yield_evm_inquirer_fixture(
     elif klass == ArbitrumOneInquirer:
         blockchain = SupportedBlockchain.ARBITRUM_ONE
 
-    if isinstance(manager_connect_at_start, str):
-        assert manager_connect_at_start == 'DEFAULT'
-        nodes_to_connect_to = database.get_rpc_nodes(blockchain, only_active=True)
-    else:
-        nodes_to_connect_to = manager_connect_at_start
-        with database.user_write() as write_cursor:
-            write_cursor.execute(  # Delete all but etherscan endpoint
-                'DELETE FROM rpc_nodes WHERE blockchain=? and endpoint!=""',
-                (blockchain.value,))
-        for entry in nodes_to_connect_to:
-            if entry.node_info.endpoint != '':  # don't re-add etherscan
-                database.add_rpc_node(entry)
+    nodes_to_connect_to = maybe_modify_rpc_nodes(database, blockchain, manager_connect_at_start)
 
     with ExitStack() as init_stack:
         if mock_other_web3 is True:
diff --git a/rotkehlchen/tests/fixtures/rotkehlchen.py b/rotkehlchen/tests/fixtures/rotkehlchen.py
--- a/rotkehlchen/tests/fixtures/rotkehlchen.py
+++ b/rotkehlchen/tests/fixtures/rotkehlchen.py
@@ -6,6 +6,7 @@
 import pytest
 
 import rotkehlchen.tests.utils.exchanges as exchange_tests
+from rotkehlchen.chain.evm.node_inquirer import _connect_task_prefix
 from rotkehlchen.constants.misc import DEFAULT_MAX_LOG_SIZE_IN_MB
 from rotkehlchen.data_migrations.constants import LAST_DATA_MIGRATION
 from rotkehlchen.db.settings import DBSettings, ModifiableDBSettings
@@ -16,6 +17,7 @@
 from rotkehlchen.rotkehlchen import Rotkehlchen
 from rotkehlchen.tests.utils.api import create_api_server
 from rotkehlchen.tests.utils.args import default_args
+from rotkehlchen.tests.utils.blockchain import maybe_modify_rpc_nodes
 from rotkehlchen.tests.utils.database import (
     _use_prepared_db,
     add_blockchain_accounts_to_db,
@@ -36,7 +38,7 @@
 from rotkehlchen.tests.utils.inquirer import inquirer_inject_ethereum_set_order
 from rotkehlchen.tests.utils.mock import mock_proxies, patch_avalanche_request
 from rotkehlchen.tests.utils.substrate import wait_until_all_substrate_nodes_connected
-from rotkehlchen.types import AVAILABLE_MODULES_MAP, Location
+from rotkehlchen.types import AVAILABLE_MODULES_MAP, Location, SupportedBlockchain
 
 
 @pytest.fixture(name='max_tasks_num')
@@ -124,6 +126,8 @@ def patch_and_enter_before_unlock(
         ksm_rpc_endpoint,
         ethereum_manager_connect_at_start,
         optimism_manager_connect_at_start,
+        polygon_pos_manager_connect_at_start,
+        arbitrum_one_manager_connect_at_start,
         kusama_manager_connect_at_start,
         have_decoders,
         use_custom_database,
@@ -133,10 +137,23 @@ def patch_and_enter_before_unlock(
 ) -> None:
     # Do not connect to the usual nodes at start by default. Do not want to spam
     # them during our tests. It's configurable per test, with the default being nothing
-    rpc_nodes_result = ethereum_manager_connect_at_start + optimism_manager_connect_at_start if network_mocking is False else []  # noqa: E501
+    def mock_get_rpc_nodes(blockchain, only_active):  # pylint: disable=unused-argument
+        if network_mocking is True:
+            return []
+
+        if blockchain == SupportedBlockchain.ETHEREUM:
+            return ethereum_manager_connect_at_start
+        elif blockchain == SupportedBlockchain.OPTIMISM:
+            return optimism_manager_connect_at_start
+        elif blockchain == SupportedBlockchain.POLYGON_POS:
+            return polygon_pos_manager_connect_at_start
+        elif blockchain == SupportedBlockchain.ARBITRUM_ONE:
+            return arbitrum_one_manager_connect_at_start
+
+        raise AssertionError(f'Got to get_rpc_nodes during test with unknown {blockchain=}')
     evm_rpcconnect_patch = patch(
         'rotkehlchen.db.dbhandler.DBHandler.get_rpc_nodes',
-        return_value=rpc_nodes_result,
+        side_effect=mock_get_rpc_nodes,
     )
 
     ksm_rpcconnect_patch = patch(
@@ -209,6 +226,8 @@ def patch_no_op_unlock(rotki, stack, should_mock_settings=True):
         ksm_rpc_endpoint='',
         ethereum_manager_connect_at_start=[],
         optimism_manager_connect_at_start=[],
+        polygon_pos_manager_connect_at_start=[],
+        arbitrum_one_manager_connect_at_start=[],
         kusama_manager_connect_at_start=[],
         have_decoders=False,
         use_custom_database=False,
@@ -239,6 +258,7 @@ def initialize_mock_rotkehlchen_instance(
         ethereum_manager_connect_at_start,
         optimism_manager_connect_at_start,
         polygon_pos_manager_connect_at_start,
+        arbitrum_one_manager_connect_at_start,
         kusama_manager_connect_at_start,
         ksm_rpc_endpoint,
         max_tasks_num,
@@ -301,6 +321,8 @@ def augmented_unlock(
             ksm_rpc_endpoint=ksm_rpc_endpoint,
             ethereum_manager_connect_at_start=ethereum_manager_connect_at_start,
             optimism_manager_connect_at_start=optimism_manager_connect_at_start,
+            polygon_pos_manager_connect_at_start=polygon_pos_manager_connect_at_start,
+            arbitrum_one_manager_connect_at_start=arbitrum_one_manager_connect_at_start,
             kusama_manager_connect_at_start=kusama_manager_connect_at_start,
             have_decoders=have_decoders,
             use_custom_database=use_custom_database,
@@ -325,6 +347,22 @@ def augmented_unlock(
     )
     # configure when task manager should run for tests
     rotki.task_manager.max_tasks_num = max_tasks_num
+    # by now DB probably has all default rpc nodes as populating rpc nodes is the default
+    # but for tests we should respect the connect_at_start fixtures
+    # also populate the nodes lists to wait for connection
+    evm_nodes_wait = []
+    for blockchain, connect_at_start, evm_manager in (
+            (SupportedBlockchain.ETHEREUM, ethereum_manager_connect_at_start, rotki.chains_aggregator.ethereum),  # noqa: E501
+            (SupportedBlockchain.OPTIMISM, optimism_manager_connect_at_start, rotki.chains_aggregator.optimism),  # noqa: E501
+            (SupportedBlockchain.POLYGON_POS, polygon_pos_manager_connect_at_start, rotki.chains_aggregator.polygon_pos),  # noqa: E501
+            (SupportedBlockchain.ARBITRUM_ONE, arbitrum_one_manager_connect_at_start, rotki.chains_aggregator.arbitrum_one),  # noqa: E501
+    ):
+        maybe_modify_rpc_nodes(rotki.data.db, blockchain, connect_at_start)
+        # since we are past evm inquirer initialization and we just wrote rpc nodes up we need to start the connection  # noqa: E501
+        evm_manager.node_inquirer.maybe_connect_to_nodes(when_tracked_accounts=True)
+        # Check if any connection tasks are pending to wait for
+        if rotki.greenlet_manager.has_task(_connect_task_prefix(evm_manager.node_inquirer.chain_name)):  # noqa: E501
+            evm_nodes_wait.append((evm_manager.node_inquirer, connect_at_start))
 
     if start_with_valid_premium:
         rotki.premium = Premium(rotki_premium_credentials)
@@ -350,18 +388,8 @@ def augmented_unlock(
         default_mock_value=default_mock_price_value,
     )
     if network_mocking is False:
-        wait_until_all_nodes_connected(
-            connect_at_start=ethereum_manager_connect_at_start,
-            evm_inquirer=rotki.chains_aggregator.ethereum.node_inquirer,
-        )
-        wait_until_all_nodes_connected(
-            connect_at_start=optimism_manager_connect_at_start,
-            evm_inquirer=rotki.chains_aggregator.optimism.node_inquirer,
-        )
-        wait_until_all_nodes_connected(
-            connect_at_start=polygon_pos_manager_connect_at_start,
-            evm_inquirer=rotki.chains_aggregator.polygon_pos.node_inquirer,
-        )
+        for evm_inquirer, connect_at_start in evm_nodes_wait:
+            wait_until_all_nodes_connected(connect_at_start=connect_at_start, evm_inquirer=evm_inquirer)  # noqa: E501
 
     wait_until_all_substrate_nodes_connected(
         substrate_manager_connect_at_start=kusama_manager_connect_at_start,
@@ -413,6 +441,7 @@ def fixture_rotkehlchen_api_server(
         ethereum_manager_connect_at_start,
         optimism_manager_connect_at_start,
         polygon_pos_manager_connect_at_start,
+        arbitrum_one_manager_connect_at_start,
         kusama_manager_connect_at_start,
         ksm_rpc_endpoint,
         max_tasks_num,
@@ -460,6 +489,7 @@ def fixture_rotkehlchen_api_server(
         ethereum_manager_connect_at_start=ethereum_manager_connect_at_start,
         optimism_manager_connect_at_start=optimism_manager_connect_at_start,
         polygon_pos_manager_connect_at_start=polygon_pos_manager_connect_at_start,
+        arbitrum_one_manager_connect_at_start=arbitrum_one_manager_connect_at_start,
         kusama_manager_connect_at_start=kusama_manager_connect_at_start,
         ksm_rpc_endpoint=ksm_rpc_endpoint,
         max_tasks_num=max_tasks_num,
@@ -485,6 +515,7 @@ def fixture_rotkehlchen_api_server(
                         ('ethereum', ethereum_manager_connect_at_start, ethereum_mock_data),
                         ('optimism', optimism_manager_connect_at_start, optimism_mock_data),
                         ('polygon_pos', [], {}),
+                        ('arbitrum_one', [], {}),
                 ):
                     maybe_mock_evm_inquirer(
                         should_mock=mock_other_web3,
@@ -526,6 +557,7 @@ def rotkehlchen_instance(
         ethereum_manager_connect_at_start,
         optimism_manager_connect_at_start,
         polygon_pos_manager_connect_at_start,
+        arbitrum_one_manager_connect_at_start,
         kusama_manager_connect_at_start,
         ksm_rpc_endpoint,
         max_tasks_num,
@@ -563,6 +595,7 @@ def rotkehlchen_instance(
         ethereum_manager_connect_at_start=ethereum_manager_connect_at_start,
         optimism_manager_connect_at_start=optimism_manager_connect_at_start,
         polygon_pos_manager_connect_at_start=polygon_pos_manager_connect_at_start,
+        arbitrum_one_manager_connect_at_start=arbitrum_one_manager_connect_at_start,
         kusama_manager_connect_at_start=kusama_manager_connect_at_start,
         ksm_rpc_endpoint=ksm_rpc_endpoint,
         max_tasks_num=max_tasks_num,
diff --git a/rotkehlchen/tests/unit/test_data_updates.py b/rotkehlchen/tests/unit/test_data_updates.py
--- a/rotkehlchen/tests/unit/test_data_updates.py
+++ b/rotkehlchen/tests/unit/test_data_updates.py
@@ -1,4 +1,6 @@
 import json
+from contextlib import ExitStack
+from typing import Callable, Optional
 from unittest.mock import patch
 
 import pytest
@@ -18,6 +20,7 @@
     EvmTokenKind,
     SupportedBlockchain,
 )
+from rotkehlchen.utils.version_check import VersionCheckResult
 
 ETHEREUM_SPAM_ASSET_ADDRESS = make_evm_address()
 OPTIMISM_SPAM_ASSET_ADDRESS = make_evm_address()
@@ -71,8 +74,55 @@
 ]
 
 
-def make_mock_github_data_response(target: UpdateType):
-    """Creates a mocking function for github requests."""
+SPAM_ASSET_MOCK_DATA = {
+    'spam_assets': [
+        {
+            'address': ETHEREUM_SPAM_ASSET_ADDRESS,
+            'name': '$ ClaimUniLP.com',
+            'symbol': '$ ClaimUniLP.com - Visit to claim',
+            'decimals': 18,
+        },
+        {
+            'address': OPTIMISM_SPAM_ASSET_ADDRESS,
+            'name': 'spooky-v3.xyz',
+            'symbol': 'Visit https://spooky-v3.xyz and claim rewards',
+            'decimals': 18,
+            'chain': 'optimism',
+        },
+    ],
+}
+
+RPC_NODE_MOCK_DATA = {
+    'rpc_nodes': [
+        {
+            'name': 'pocket network',
+            'endpoint': 'https://eth-mainnet.gateway.pokt.network/v1/5f3453978e354ab992c4da79',
+            'weight': 0.50,
+            'owned': False,
+            'active': True,
+            'blockchain': 'ETH',
+        },
+        {
+            'name': 'alchemy free',
+            'endpoint': 'https://mainnet.optimism.io',
+            'weight': 0.50,
+            'owned': False,
+            'active': True,
+            'blockchain': 'OPTIMISM',
+        },
+    ],
+}
+
+CONTRACTS_MOCK_DATA = {
+    'contracts': {
+        'abis_data': ABI_DATA,
+        'contracts_data': CONTRACT_DATA,
+    },
+}
+
+
+def make_single_mock_github_data_response(target: UpdateType):
+    """Creates a mocking function for a single update type for github requests."""
     def mock_github_data_response(url, timeout):  # pylint: disable=unused-argument
         if 'info' in url:
             data = {
@@ -82,79 +132,80 @@ def mock_github_data_response(url, timeout):  # pylint: disable=unused-argument
                 'global_addressbook': {'latest': 1 if target == UpdateType.GLOBAL_ADDRESSBOOK else 0},  # noqa: E501
             }
         elif 'spam_assets/v' in url:
-            data = {
-                'spam_assets': [
-                    {
-                        'address': ETHEREUM_SPAM_ASSET_ADDRESS,
-                        'name': '$ ClaimUniLP.com',
-                        'symbol': '$ ClaimUniLP.com - Visit to claim',
-                        'decimals': 18,
-                    },
-                    {
-                        'address': OPTIMISM_SPAM_ASSET_ADDRESS,
-                        'name': 'spooky-v3.xyz',
-                        'symbol': 'Visit https://spooky-v3.xyz and claim rewards',
-                        'decimals': 18,
-                        'chain': 'optimism',
-                    },
-                ],
-            }
+            data = SPAM_ASSET_MOCK_DATA
         elif 'rpc_nodes/v' in url:
-            data = {
-                'rpc_nodes': [
-                    {
-                        'name': 'pocket network',
-                        'endpoint': 'https://eth-mainnet.gateway.pokt.network/v1/5f3453978e354ab992c4da79',  # noqa: E501
-                        'weight': 0.50,
-                        'owned': False,
-                        'active': True,
-                        'blockchain': 'ETH',
-                    },
-                    {
-                        'name': 'alchemy free',
-                        'endpoint': 'https://mainnet.optimism.io',
-                        'weight': 0.50,
-                        'owned': False,
-                        'active': True,
-                        'blockchain': 'OPTIMISM',
-                    },
-                ],
-            }
+            data = RPC_NODE_MOCK_DATA
         elif 'contracts/v' in url:
-            data = {
-                'contracts': {
-                    'abis_data': ABI_DATA,
-                    'contracts_data': CONTRACT_DATA,
-                },
-            }
+            data = CONTRACTS_MOCK_DATA
         elif 'global_addressbook/v' in url:
             data = {'global_addressbook': SERIALIZED_REMOTE_ADDRESSBOOK}
         else:
-            raise AssertionError(f'Unexpected url {url} called')
+            raise AssertionError(f'Unknown {url=} during test')
 
         return MockResponse(200, json.dumps(data))
 
     return mock_github_data_response
 
 
-def mock_github_data_response_old_update(url, timeout):  # pylint: disable=unused-argument
-    if 'info' not in url:
-        raise AssertionError(f'Unexpected url {url} called')
+def make_mock_github_response(latest: int, min_version: Optional[str] = None, max_version: Optional[str] = None) -> Callable:  # noqa: E501
+    """Creates a mocking function for all update types for github requests."""
+    def mock_github_response(url, timeout):  # pylint: disable=unused-argument
+        if 'info' in url:
+            result = {}
+            for update_type in UpdateType:
+                entry = {'latest': latest}
+                for i in range(1, latest + 1):
+                    if min_version or max_version:
+                        if 'limits' not in entry:  # not using defaultdict to see that if missing it's also handled by the code fine  # noqa: E501
+                            entry['limits'] = {}
+                        entry['limits'][i] = {}
+                        if min_version:
+                            entry['limits'][i]['min_version'] = min_version
+                        if max_version:
+                            entry['limits'][i]['max_version'] = max_version
+                result[update_type.value] = entry
+        elif 'spam_assets/v' in url:
+            result = SPAM_ASSET_MOCK_DATA
+        elif 'rpc_nodes/v' in url:
+            result = RPC_NODE_MOCK_DATA
+        elif 'contracts/v' in url:
+            result = CONTRACTS_MOCK_DATA
+        elif 'global_addressbook/v' in url:
+            result = {'global_addressbook': SERIALIZED_REMOTE_ADDRESSBOOK}
+        else:
+            raise AssertionError(f'Unknown {url=} during test')
 
-    return MockResponse(200, json.dumps({
-        'spam_assets': {'latest': 1},
-        'rpc_nodes': {'latest': 1},
-        'contracts': {'latest': 1},
-        'global_addressbook': {'latest': 1},
-    }))
+        return MockResponse(200, json.dumps(result))
+
+    return mock_github_response
+
+
+@pytest.fixture(name='our_version')
+def fixture_our_version():
+    """Allow mocking our rotki version since in CI version is 0 and hard to control"""
+    return None
 
 
 @pytest.fixture(name='data_updater')
-def fixture_data_updater(messages_aggregator, database):
-    return RotkiDataUpdater(
-        msg_aggregator=messages_aggregator,
-        user_db=database,
-    )
+def fixture_data_updater(messages_aggregator, database, our_version):
+    """Initialize the DataUpdater object, optionally mocking our rotki version"""
+    with ExitStack() as stack:
+        if our_version is not None:
+            stack.enter_context(patch('rotkehlchen.db.updates.get_current_version', return_value=VersionCheckResult(our_version=our_version)))  # noqa: E501
+        return RotkiDataUpdater(
+            msg_aggregator=messages_aggregator,
+            user_db=database,
+        )
+
+
+def reset_update_type_mappings(data_updater) -> None:
+    """Need to use this after mocking so that the mapping points to the mocked methods"""
+    data_updater.update_type_mappings = {
+        UpdateType.SPAM_ASSETS: data_updater.update_spam_assets,
+        UpdateType.RPC_NODES: data_updater.update_rpc_nodes,
+        UpdateType.CONTRACTS: data_updater.update_contracts,
+        UpdateType.GLOBAL_ADDRESSBOOK: data_updater.update_global_addressbook,
+    }
 
 
 def test_update_type_mappings_is_complete(data_updater: RotkiDataUpdater) -> None:
@@ -179,7 +230,7 @@ def test_update_spam_assets(data_updater: RotkiDataUpdater) -> None:
         EvmToken(op_spam_token_id)
 
     # set a high version of the globaldb to avoid conflicts with future changes
-    with patch('requests.get', wraps=make_mock_github_data_response(UpdateType.SPAM_ASSETS)):
+    with patch('requests.get', wraps=make_single_mock_github_data_response(UpdateType.SPAM_ASSETS)):  # noqa: E501
         data_updater.check_for_updates()
 
     ethereum_token = EvmToken(eth_spam_token_id)
@@ -193,10 +244,33 @@ def test_update_spam_assets(data_updater: RotkiDataUpdater) -> None:
         assert cursor.fetchall() == [(eth_spam_token_id,), (op_spam_token_id,)]
 
 
-def test_no_update_performed(data_updater: RotkiDataUpdater) -> None:
+def test_updates_run(data_updater: RotkiDataUpdater) -> None:
     """
-    Check that the updates don't execute if we have a higher version locally
+    Check that the invdividual update functions execute as many times as needed
+    due to latest. This is also a way to check the subsequent tests for updates
+    not executing are valid
     """
+    times = 2
+    with ExitStack() as stack:
+        stack.enter_context(patch('requests.get', wraps=make_mock_github_response(latest=times)))
+        patches = [
+            stack.enter_context(patch.object(data_updater, f'update_{update_type.value}'))
+            for update_type in UpdateType
+        ]
+        reset_update_type_mappings(data_updater)
+        data_updater.check_for_updates()
+
+    assert all(patch.call_count == times for patch in patches)
+    with data_updater.user_db.conn.read_ctx() as cursor:
+        cursor.execute(  # make sure latest DB value is also changed
+            'SELECT value from settings WHERE name IN(?, ?, ?, ?)',
+            [x.serialize() for x in UpdateType],
+        )
+        assert {x[0] for x in cursor} == {'2'}
+
+
+def test_no_update_due_to_update_version(data_updater: RotkiDataUpdater) -> None:
+    """Check that the updates don't execute if we have a higher update version locally"""
     # set a higher last version applied
     with data_updater.user_db.conn.write_ctx() as write_cursor:
         write_cursor.executemany(
@@ -208,19 +282,64 @@ def test_no_update_performed(data_updater: RotkiDataUpdater) -> None:
                 (UpdateType.GLOBAL_ADDRESSBOOK.serialize(), 999),
             ],
         )
+    with ExitStack() as stack:
+        stack.enter_context(patch('requests.get', wraps=make_mock_github_response(latest=1)))
+        patches = [
+            stack.enter_context(patch.object(data_updater, f'update_{update_type.value}'))
+            for update_type in UpdateType
+        ]
+        reset_update_type_mappings(data_updater)
+        data_updater.check_for_updates()
+
+    assert all(patch.call_count == 0 for patch in patches)
+    with data_updater.user_db.conn.read_ctx() as cursor:
+        cursor.execute(  # also make sure latest DB value is not changed
+            'SELECT value from settings WHERE name IN(?, ?, ?, ?)',
+            [x.serialize() for x in UpdateType],
+        )
+        assert {x[0] for x in cursor} == {'999'}
+
+
+@pytest.mark.parametrize('our_version', ['1.35.0'])  # set a normal version for CI
+def test_no_update_due_to_min_rotki(data_updater: RotkiDataUpdater) -> None:
+    """Check updates don't execute if there is a min rotki version requirement greater than ours"""
+    with ExitStack() as stack:
+        stack.enter_context(patch('requests.get', wraps=make_mock_github_response(latest=1, min_version='99.99.99')))  # noqa: E501
+        patches = [
+            stack.enter_context(patch.object(data_updater, f'update_{update_type.value}'))
+            for update_type in UpdateType
+        ]
+        reset_update_type_mappings(data_updater)
+        data_updater.check_for_updates()
 
-    with (
-        patch('requests.get', wraps=mock_github_data_response_old_update),
-        patch.object(data_updater, 'update_spam_assets') as spam_assets,
-        patch.object(data_updater, 'update_rpc_nodes') as rpc_nodes,
-        patch.object(data_updater, 'update_contracts') as contracts,
-        patch.object(data_updater, 'update_global_addressbook') as global_addressbook,
-    ):
+    assert all(patch.call_count == 0 for patch in patches)
+    with data_updater.user_db.conn.read_ctx() as cursor:
+        cursor.execute(  # also make sure latest DB value is not changed
+            'SELECT value from settings WHERE name IN(?, ?, ?, ?)',
+            [x.serialize() for x in UpdateType],
+        )
+        assert {x[0] for x in cursor} == set()
+
+
+@pytest.mark.parametrize('our_version', ['1.35.0'])  # set a normal version for CI
+def test_no_update_due_to_max_rotki(data_updater: RotkiDataUpdater) -> None:
+    """Check updates don't execute if there is a max rotki version requirement lower than ours"""
+    with ExitStack() as stack:
+        stack.enter_context(patch('requests.get', wraps=make_mock_github_response(latest=1, max_version='1.0.0')))  # noqa: E501
+        patches = [
+            stack.enter_context(patch.object(data_updater, f'update_{update_type.value}'))
+            for update_type in UpdateType
+        ]
+        reset_update_type_mappings(data_updater)
         data_updater.check_for_updates()
-        assert spam_assets.call_count == 0
-        assert rpc_nodes.call_count == 0
-        assert contracts.call_count == 0
-        assert global_addressbook.call_count == 0
+
+    assert all(patch.call_count == 0 for patch in patches)
+    with data_updater.user_db.conn.read_ctx() as cursor:
+        cursor.execute(  # also make sure latest DB value is not changed
+            'SELECT value from settings WHERE name IN(?, ?, ?, ?)',
+            [x.serialize() for x in UpdateType],
+        )
+        assert {x[0] for x in cursor} == set()
 
 
 def test_update_rpc_nodes(data_updater: RotkiDataUpdater) -> None:
@@ -244,7 +363,7 @@ def test_update_rpc_nodes(data_updater: RotkiDataUpdater) -> None:
         write_cursor.execute('SELECT COUNT(*) FROM rpc_nodes')
         assert write_cursor.fetchone()[0] == 22
 
-    with patch('requests.get', wraps=make_mock_github_data_response(UpdateType.RPC_NODES)):
+    with patch('requests.get', wraps=make_single_mock_github_data_response(UpdateType.RPC_NODES)):
         data_updater.check_for_updates()
 
     # check the db state after updating
@@ -274,7 +393,7 @@ def test_update_contracts(data_updater: RotkiDataUpdater) -> None:
         initial_contracts = cursor.execute('SELECT * FROM contract_data').fetchall()
         assert len(initial_contracts) > 0, 'There should be some contracts in the db'
 
-    with patch('requests.get', wraps=make_mock_github_data_response(UpdateType.CONTRACTS)):  # noqa: E501
+    with patch('requests.get', wraps=make_single_mock_github_data_response(UpdateType.CONTRACTS)):  # noqa: E501
         data_updater.check_for_updates()  # apply the update
 
     remote_id_to_local_id = {}
@@ -331,7 +450,7 @@ def test_global_addressbook(data_updater: RotkiDataUpdater) -> None:
             entries=initial_entries,
         )
 
-    with patch('requests.get', wraps=make_mock_github_data_response(UpdateType.GLOBAL_ADDRESSBOOK)):  # noqa: E501
+    with patch('requests.get', wraps=make_single_mock_github_data_response(UpdateType.GLOBAL_ADDRESSBOOK)):  # noqa: E501
         data_updater.check_for_updates()
 
     # Assert state of the address book after the update
diff --git a/rotkehlchen/tests/utils/blockchain.py b/rotkehlchen/tests/utils/blockchain.py
--- a/rotkehlchen/tests/utils/blockchain.py
+++ b/rotkehlchen/tests/utils/blockchain.py
@@ -1,4 +1,5 @@
 import json
+from collections.abc import Sequence
 from typing import TYPE_CHECKING, Any, Optional, Union
 from unittest.mock import patch
 
@@ -28,6 +29,8 @@
 
     from rotkehlchen.chain.aggregator import ChainsAggregator
     from rotkehlchen.chain.ethereum.node_inquirer import EthereumInquirer
+    from rotkehlchen.chain.evm.types import WeightedNode
+    from rotkehlchen.db.dbhandler import DBHandler
 
 
 def assert_btc_balances_result(
@@ -679,3 +682,36 @@ def mock_arbitrum_one_has_activity(account):
         side_effect=mock_arbitrum_one_has_activity,
     ))
     return stack
+
+
+def maybe_modify_rpc_nodes(
+        database: 'DBHandler',
+        blockchain: SupportedBlockchain,
+        manager_connect_at_start: Union[str, Sequence['WeightedNode']],
+) -> Sequence['WeightedNode']:
+    """Modify the rpc nodes in the DB for the given blockchain depending on
+    the value of the managager_connect_at_start.
+
+    IF it's a string can only be default. In which case we get the nodes
+    to connect to directly as default from the DB.
+
+    But if it's a tuple of entries we clean the DB except the etherscan entry
+    and write that tuple. Tuple may also be empty which results in clean all nodes
+    except etherscan for the chain.
+
+    Returns the nodes to connect to.
+    """
+    if isinstance(manager_connect_at_start, str):
+        assert manager_connect_at_start == 'DEFAULT'
+        nodes_to_connect_to = database.get_rpc_nodes(blockchain, only_active=True)
+    else:
+        nodes_to_connect_to = manager_connect_at_start
+        with database.user_write() as write_cursor:
+            write_cursor.execute(  # Delete all but etherscan endpoint
+                'DELETE FROM rpc_nodes WHERE blockchain=? and endpoint!=""',
+                (blockchain.value,))
+        for entry in nodes_to_connect_to:
+            if entry.node_info.endpoint != '':  # don't re-add etherscan
+                database.add_rpc_node(entry)
+
+    return nodes_to_connect_to