From 62d32a1c2cf399d86cc7359453793109966b4498 Mon Sep 17 00:00:00 2001 From: OjusWiZard Date: Fri, 17 May 2024 18:24:04 +0530 Subject: [PATCH] Fix (global.db): Test consistency with updates Signed-off-by: OjusWiZard --- rotkehlchen/data/global.db | Bin 6893568 -> 6905856 bytes rotkehlchen/globaldb/updates.py | 9 +- .../tests/unit/globaldb/test_upgrades.py | 349 +++++++++++++++++- 3 files changed, 354 insertions(+), 4 deletions(-) diff --git a/rotkehlchen/data/global.db b/rotkehlchen/data/global.db index 569c772e2a3b28155977b1d4f401040a160d3f63..59a7e7e6bb27b9c05224421e403c4c8eb9f06d62 100644 GIT binary patch delta 23564 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 delta 18284 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 diff --git a/rotkehlchen/globaldb/updates.py b/rotkehlchen/globaldb/updates.py index 073578fec9..0534ce63e3 100644 --- a/rotkehlchen/globaldb/updates.py +++ b/rotkehlchen/globaldb/updates.py @@ -434,7 +434,14 @@ def _handle_asset_update( try: with connection.savepoint_ctx() as cursor: - executeall(cursor, action) + if action.strip().startswith('UPDATE') and cursor.execute( + 'SELECT COUNT(*) FROM assets WHERE identifier=?', + (remote_asset_data.identifier,), + ).fetchone()[0] == 0: + executeall(cursor, full_insert) + else: + executeall(cursor, action) + if local_asset is not None: AssetResolver().clean_memory_cache(identifier=local_asset.identifier) except sqlite3.Error: # https://docs.python.org/3/library/sqlite3.html#exceptions diff --git a/rotkehlchen/tests/unit/globaldb/test_upgrades.py b/rotkehlchen/tests/unit/globaldb/test_upgrades.py index e4828ea158..2ba5f5c6cf 100644 --- a/rotkehlchen/tests/unit/globaldb/test_upgrades.py +++ b/rotkehlchen/tests/unit/globaldb/test_upgrades.py @@ -1,18 +1,26 @@ import json import shutil +from collections import defaultdict from contextlib import ExitStack +from dataclasses import dataclass from pathlib import Path from sqlite3 import IntegrityError +from typing import TYPE_CHECKING, Any, Final +from urllib import request +from warnings import warn import pytest from eth_utils.address import to_checksum_address from freezegun import freeze_time +from rotkehlchen.assets.asset import Asset from rotkehlchen.assets.types import AssetType -from rotkehlchen.constants.misc import GLOBALDB_NAME, GLOBALDIR_NAME +from rotkehlchen.chain.evm.decoding.aave.constants import CPT_AAVE_V3 +from rotkehlchen.constants.misc import GLOBALDB_NAME, GLOBALDIR_NAME, ONE from rotkehlchen.db.drivers.gevent import DBConnection, DBConnectionType from rotkehlchen.db.utils import table_exists from rotkehlchen.errors.misc import DBUpgradeError +from rotkehlchen.fval import FVal from rotkehlchen.globaldb.cache import ( globaldb_get_general_cache_keys_and_values_like, globaldb_get_general_cache_last_queried_ts_by_key, @@ -23,6 +31,7 @@ DB_CREATE_LOCATION_ASSET_MAPPINGS, DB_CREATE_LOCATION_UNSUPPORTED_ASSETS, ) +from rotkehlchen.globaldb.updates import AssetsUpdater from rotkehlchen.globaldb.upgrades.manager import maybe_upgrade_globaldb from rotkehlchen.globaldb.upgrades.v2_v3 import OTHER_EVM_CHAINS_ASSETS from rotkehlchen.globaldb.upgrades.v3_v4 import ( @@ -39,18 +48,75 @@ from rotkehlchen.tests.fixtures.globaldb import create_globaldb from rotkehlchen.tests.utils.globaldb import patch_for_globaldb_upgrade_to from rotkehlchen.types import ( + AERODROME_POOL_PROTOCOL, + CURVE_POOL_PROTOCOL, + PICKLE_JAR_PROTOCOL, + SPAM_PROTOCOL, + VELODROME_POOL_PROTOCOL, YEARN_VAULTS_V1_PROTOCOL, + YEARN_VAULTS_V2_PROTOCOL, CacheType, ChainID, + ChecksumEvmAddress, EvmTokenKind, Location, Timestamp, ) from rotkehlchen.utils.misc import ts_now +if TYPE_CHECKING: + from rotkehlchen.user_messages import MessagesAggregator + + # TODO: Perhaps have a saved version of that global DB for the tests and query it too? -ASSETS_IN_V2_GLOBALDB = 3095 -YEARN_V1_ASSETS_IN_V3 = 32 +ASSETS_IN_V2_GLOBALDB: Final = 3095 +YEARN_V1_ASSETS_IN_V3: Final = 32 +ASSET_UPDATE: Final = "[('{address}', Chain.{chain_name}, '{coingecko}', '{cryptocompare}', {field_updates}, '{protocol}', {underlying_token_addresses})]," # noqa: E501 +ASSET_COLLECTION_UPDATE: Final = 'INSERT INTO asset_collections(id, name, symbol) VALUES ({collection}, "{name}", "{symbol}");' # noqa: E501 +ASSET_MAPPING: Final = 'INSERT INTO multiasset_mappings(collection_id, asset) VALUES ({collection}, "{id}");' # noqa: E501 + +IGNORED_PROTOCOLS: Final = { + CURVE_POOL_PROTOCOL, + YEARN_VAULTS_V1_PROTOCOL, + YEARN_VAULTS_V2_PROTOCOL, + VELODROME_POOL_PROTOCOL, + AERODROME_POOL_PROTOCOL, + PICKLE_JAR_PROTOCOL, + SPAM_PROTOCOL, + CPT_AAVE_V3, +} + + +@dataclass(init=True, repr=False, eq=False, order=False, unsafe_hash=False, frozen=False) +class DBToken: + address: ChecksumEvmAddress + chain: int + token_kind: str + name: str + symbol: str + started: Timestamp | None = None + forked: str | None = None + swapped_for: str | None = None + coingecko: str | None = None + cryptocompare: str | None = None + decimals: int | None = None + protocol: str | None = None + + def as_dict(self) -> dict[str, Any]: + return { + 'address': self.address, + 'chain': self.chain, + 'token_kind': self.token_kind, + 'name': self.name, + 'symbol': self.symbol, + 'started': self.started, + 'forked': self.forked, + 'swapped_for': self.swapped_for, + 'coingecko': self.coingecko, + 'cryptocompare': self.cryptocompare, + 'decimals': self.decimals, + 'protocol': self.protocol, + } def _count_sql_file_sentences(file_name: str, skip_statements: int = 0): @@ -664,3 +730,280 @@ def test_applying_all_upgrade(globaldb: GlobalDBHandler): assert globaldb.get_setting_value('version', 0) == GLOBAL_DB_VERSION with globaldb.conn.cursor() as cursor: assert cursor.execute('SELECT COUNT(*) from assets WHERE identifier="eip155:/erc20:0x32c6fcC9bC912C4A30cd53D2E606461e44B77AF2"').fetchone()[0] == 1 # noqa: E501 + + +def test_update_consistency_with_packaged_db( + tmpdir_factory: 'pytest.TempdirFactory', + messages_aggregator: 'MessagesAggregator', +): + """Test that the globalDB updates are consistent with the packaged one. + - All assets are present in both cases. + - All details of these assets are the same in both cases. + - All underlying assets are present and mapped in both cases. + Protocol tokens that are queried automatically are not tested here.""" + temp_data_dir = Path(tmpdir_factory.mktemp(GLOBALDIR_NAME)) + (old_db_dir := temp_data_dir / GLOBALDIR_NAME).mkdir(parents=True, exist_ok=True) + request.urlretrieve( + url='https://github.com/rotki/rotki/raw/v1.26.0/rotkehlchen/data/global.db', + filename=old_db_dir / 'global.db', + ) + + globaldb = create_globaldb(data_directory=temp_data_dir, sql_vm_instructions_cb=0) + + with ( + globaldb.conn.read_ctx() as old_db_cursor, + globaldb.packaged_db_conn().read_ctx() as packaged_db_cursor, + ): + assert old_db_cursor.execute('SELECT value FROM settings WHERE name="assets_version"').fetchone()[0] == '15' # noqa: E501 + assert packaged_db_cursor.execute('SELECT value FROM settings WHERE name="assets_version"').fetchone()[0] == '24' # noqa: E501 + + assets_updater = AssetsUpdater(msg_aggregator=messages_aggregator) + if (conflicts := assets_updater.perform_update( + up_to_version=None, + conflicts=None, + )) is not None: + assert assets_updater.perform_update( + up_to_version=None, + conflicts={ + Asset(conflict['identifier']): 'remote' + for conflict in conflicts + }, + ) is None + + with ( + globaldb.conn.read_ctx() as old_db_cursor, + globaldb.packaged_db_conn().read_ctx() as packaged_db_cursor, + ): + ignored_identifiers = { # prepare a set of identifiers that should be ignored + identifiers[0] + for cursor in (old_db_cursor, packaged_db_cursor) + for identifiers in cursor.execute( + f'SELECT identifier FROM evm_tokens WHERE ' + f'protocol IN ({",".join(["?"] * len(IGNORED_PROTOCOLS))}) OR ' + f'address in (SELECT value FROM general_cache)', + list(IGNORED_PROTOCOLS), + ).fetchall() + } + + ( + (updated_assets, updated_underlying_db_assets, updated_collections, updated_multiasset_mappings), # noqa: E501 + (packaged_assets, packaged_underlying_db_assets, packaged_collections, packaged_multiasset_mappings), # noqa: E501 + ) = ( + ( + { + identifier: DBToken( + address=address, + chain=chain, + token_kind=token_kind, + decimals=decimals, + name=name, + symbol=symbol, + started=started, + swapped_for=swapped_for, + coingecko=coingecko, + cryptocompare=cryptocompare, + protocol=protocol, + ) + for (identifier, address, chain, token_kind, decimals, name, symbol, started, swapped_for, coingecko, cryptocompare, protocol) # noqa: E501 + in cursor.execute( + 'SELECT A.identifier, B.address, B.chain, B.token_kind, B.decimals, ' + 'C.name, A.symbol, A.started, A.swapped_for, A.coingecko, ' + 'A.cryptocompare, B.protocol FROM evm_tokens AS B JOIN ' + 'common_asset_details AS A ON B.identifier = A.identifier ' + 'JOIN assets AS C on C.identifier=A.identifier', + ) + if identifier not in ignored_identifiers + }, cursor.execute( + 'SELECT identifier, weight, parent_token_entry FROM underlying_tokens_list', + ).fetchall(), { + collection_id: (name, symbol) + for (collection_id, name, symbol) + in cursor.execute('SELECT id, name, symbol FROM asset_collections') + }, cursor.execute( + 'SELECT collection_id, asset FROM multiasset_mappings', + ).fetchall(), + ) + for cursor in (old_db_cursor, packaged_db_cursor) + ) + + missing_in_updates = [] + # find all the asset collections that are present in asset updates but not in packaged DB # noqa: E501 + missing_in_packaged_db = [ + f'Asset collection id: {group_id} ({updated_collections[group_id][1]}) not found in packaged DB.' # noqa: E501 + for group_id in updated_collections + if group_id not in packaged_collections + ] + + # find all the asset collections that are present in packaged DB but not in asset updates # noqa: E501 + for group_id in packaged_collections: + if group_id not in updated_collections: + assert None not in (packaged_collections[group_id][i] for i in (0, 1)) + missing_in_updates.append(ASSET_COLLECTION_UPDATE.format( + collection=group_id, + name=packaged_collections[group_id][0], + symbol=packaged_collections[group_id][1], + )) + continue + + if packaged_collections[group_id] != updated_collections[group_id]: + missing_in_packaged_db.append(f'Asset collection id: {group_id} ({packaged_collections[group_id][1]}) - {packaged_collections[group_id]} (packaged) != {updated_collections[group_id]} (asset update)') # noqa: E501 + + # fin all the multiasset mappings that are present in asset updates but not in packaged DB + missing_in_packaged_db.extend([ + f'Multiasset mapping {mapping} not found in packaged DB.' + for mapping in updated_multiasset_mappings + if mapping not in packaged_multiasset_mappings + ]) + + # find all the multiasset mappings that are present in packaged DB but not in asset updates + for mapping in packaged_multiasset_mappings: + assert None not in (mapping[i] for i in (0, 1)) + if mapping not in updated_multiasset_mappings: + missing_in_updates.append(ASSET_MAPPING.format(collection=mapping[0], id=mapping[1])) + + field_to_table = { # a mapping from EvmTokens field names to the DB table they are stored in + 'name': 'assets', + 'address': 'evm_tokens', + 'chain': 'evm_tokens', + 'token_kind': 'evm_tokens', + 'decimals': 'evm_tokens', + 'protocol': 'evm_tokens', + 'symbol': 'common_asset_details', + 'started': 'common_asset_details', + 'swapped_for': 'common_asset_details', + 'coingecko': 'common_asset_details', + 'cryptocompare': 'common_asset_details', + } + + # populate mappings from parent token to set of underlying tokens for both DBs + updated_underlying_assets: dict[str, set[str]] = defaultdict(set) + packaged_underlying_assets: dict[str, set[str]] = defaultdict(set) + for underlying_db_assets, underlying_assets in ( + (updated_underlying_db_assets, updated_underlying_assets), + (packaged_underlying_db_assets, packaged_underlying_assets), + ): + for underlying_identifier, weight, parent_identifier in underlying_db_assets: + if FVal(weight) != ONE: + warn(f'Underlying asset {underlying_identifier} weight is not 1: {weight}. Skipping its DB check. Check it manually.') # noqa: E501 + continue + + underlying_assets[parent_identifier].add(underlying_identifier) + + # find all the assets that are present in asset updates but not in packaged DB + missing_in_packaged_db.extend([ + f'Asset: {identifier} ({updated_assets[identifier].symbol}) not found in packaged DB. Protocol: {updated_assets[identifier].protocol}' # noqa: E501 + for identifier in updated_assets + if identifier not in packaged_assets + ]) + + # find all the assets that are present in packaged DB but not in asset updates + for identifier in packaged_assets: + if identifier not in updated_assets: + missing_in_updates.append(ASSET_UPDATE.format( + address=packaged_assets[identifier].address, + chain_name=ChainID.deserialize(packaged_assets[identifier].chain).name, + coingecko=packaged_assets[identifier].coingecko, + cryptocompare=packaged_assets[identifier].cryptocompare, + field_updates={}, + protocol=packaged_assets[identifier].protocol, + underlying_token_addresses=[ + packaged_assets[identifier].address + for identifier in packaged_underlying_assets[identifier] + ], + )) + continue + + for assets in (updated_assets, packaged_assets): + for key, value in assets[identifier].as_dict().items(): + if value == '': + setattr(assets[identifier], key, None) + + # if the updated asset values are different from the packaged asset values, prepare their updates + if packaged_assets[identifier].as_dict() != updated_assets[identifier].as_dict(): + updates: dict[str, dict[str, str]] = defaultdict(dict) # mapping from table name to mapping from column name to value, that needs to update # noqa: E501 + fields_to_update: dict = {'underlying_tokens': []} + for key, packaged_field in packaged_assets[identifier].as_dict().items(): + updated_field = getattr(updated_assets[identifier], key) + if packaged_field is not None and updated_field != packaged_field: + updates[field_to_table[key]][key] = packaged_field + fields_to_update[key] = packaged_field + + elif updated_field is not None and packaged_field is None: + missing_in_packaged_db.append(f'Asset: {identifier} ({key}) - {packaged_field} (packaged) != {updated_field} (asset update)') # noqa: E501 + fields_to_update[key] = updated_field + + else: + fields_to_update[key] = packaged_field + + if packaged_underlying_assets[identifier] != updated_underlying_assets[identifier]: + if len(updated_underlying_assets[identifier] - packaged_underlying_assets[identifier]) > 0: # noqa: E501 + missing_in_packaged_db.append(f'Asset: {identifier} ({key}) - {packaged_field} (packaged) != {updated_field} (asset update)') # noqa: E501 # pylint: disable=undefined-loop-variable + + if len(underlying_tokens_to_update := updated_underlying_assets[identifier] - packaged_underlying_assets[identifier]) > 0: # noqa: E501 + fields_to_update['underlying_tokens'] = [ + updated_assets[identifier].address + for identifier in underlying_tokens_to_update + ] + + if len(updates) > 0: + missing_in_updates.append(ASSET_UPDATE.format( + address=fields_to_update['address'], + chain_name=ChainID.deserialize(fields_to_update['chain']).name, + coingecko=fields_to_update['coingecko'], + cryptocompare=fields_to_update['cryptocompare'], + field_updates=dict(updates), + protocol=fields_to_update['protocol'], + underlying_token_addresses=fields_to_update['underlying_tokens'], + )) + + missing_in_updates = [ + str(update).replace('None', '') + for update in missing_in_updates + ] + + if missing_in_packaged_db != []: + warn('\n'.join(missing_in_packaged_db)) + + if missing_in_updates != []: + pytest.fail('\n'.join(missing_in_updates)) + + +def test_oracle_ids_in_asset_collections(globaldb: 'GlobalDBHandler'): + """Test that for each asset in a collection, their oracle IDs are same.""" + + with globaldb.conn.read_ctx() as cursor: + assets = { + identifier: { + 'coingecko': coingecko, + 'cryptocompare': cryptocompare, + } + for (identifier, coingecko, cryptocompare) + in cursor.execute( + 'SELECT identifier, coingecko, cryptocompare FROM common_asset_details', + ) + } + + multiasset_mappings = cursor.execute( + 'SELECT collection_id, asset FROM multiasset_mappings', + ).fetchall() + + asset_to_group_id = {} + group_id_to_assets = defaultdict(set) + for mapping in multiasset_mappings: + group_id_to_assets[mapping[0]].add(mapping[1]) + asset_to_group_id[mapping[1]] = mapping[0] + + mismatches = [] + group_id_to_oracle_ids: dict[str, dict[str, str]] = defaultdict(dict) + for oracle in ('coingecko', 'cryptocompare'): + for group_id, asset_collection in group_id_to_assets.items(): + for identifier in asset_collection: + if identifier in assets and assets[identifier][oracle] not in {None, ''}: + if group_id_to_oracle_ids[group_id].get(oracle) not in {None, ''}: + if assets[identifier][oracle].lower() != group_id_to_oracle_ids[group_id][oracle].lower(): # noqa: E501 + mismatches.append(f'{oracle} ({assets[identifier][oracle]} != {group_id_to_oracle_ids[group_id][oracle]}) mismatch for asset {identifier} in group {group_id}') # noqa: E501 + else: + group_id_to_oracle_ids[group_id][oracle] = assets[identifier][oracle] + + if len(mismatches) > 0: + pytest.fail('\n'.join(mismatches))