Skip to content

Commit

Permalink
Add API endpoint for replacing an asset with another one in the DB
Browse files Browse the repository at this point in the history
Fix #2898
  • Loading branch information
LefterisJP committed May 20, 2021
1 parent f758e12 commit 9f3a129
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 0 deletions.
38 changes: 38 additions & 0 deletions docs/api.rst
Expand Up @@ -2711,6 +2711,44 @@ Performing an asset update
:statuscode 500: Internal Rotki error
:statuscode 502: Error while trying to reach the remote for asset updates.

Replacing an asset
========================

.. http:put:: /api/(version)/assets/replace
It's possible to replace an asset with another asset in both the global and the user DB. If the source asset identifier exists in the global DB it's removed in favor of the target asset. If not, the global DB is not touched. In both cases the user DB is touched and all appearances of the source asset identifier are replaced with target asset.
If the asset you are replacing is used as swapped_for, forked_for or underlying asset by another asset you will first have to manually delete it from there.

**Example Request**:

.. http:example:: curl wget httpie python-requests
PUT /api/1/assets/replace HTTP/1.1
Host: localhost:5042
Content-Type: application/json;charset=UTF-8

{"source_identifier": "4979582b-ee8c-4d45-b461-15c4220de666", "target_asset": "BTC"}

:reqjson string source_identifier: The identifier of the asset that will get replaced/removed. This asset does not need to exist in the global DB. If it does it will be removed.
:reqjson string target_asset: The identifier of the asset that will replace the source asset. This must be an existing asset.

**Example Response**:

.. sourcecode:: http

HTTP/1.1 200 OK
Content-Type: application/json

{
"result": true,
"message": ""
}

:statuscode 200: Asset succesfully replaced.
:statuscode 400: Provided JSON is in some way malformed
:statuscode 409: Some conflict at replacing or user is not loggged in.
:statuscode 500: Internal Rotki error

Querying asset icons
======================

Expand Down
12 changes: 12 additions & 0 deletions rotkehlchen/api/rest.py
Expand Up @@ -67,6 +67,7 @@
RotkehlchenPermissionError,
SystemPermissionError,
TagConstraintError,
UnknownAsset,
UnsupportedAsset,
)
from rotkehlchen.exchanges.data_structures import Trade
Expand Down Expand Up @@ -1372,6 +1373,17 @@ def delete_custom_asset(self, identifier: str) -> Response:
AssetResolver().assets_cache.pop(identifier, None)
return api_response(OK_RESULT, status_code=HTTPStatus.OK)

@require_loggedin_user()
def replace_asset(self, source_identifier: str, target_asset: Asset) -> Response:
try:
self.rotkehlchen.data.db.replace_asset_identifier(source_identifier, target_asset)
except (UnknownAsset, InputError) as e:
return api_response(wrap_in_fail_result(str(e)), status_code=HTTPStatus.CONFLICT)

# Also clear the in-memory cache of the asset resolver
AssetResolver().assets_cache.pop(source_identifier, None)
return api_response(OK_RESULT, status_code=HTTPStatus.OK)

@staticmethod
def get_custom_ethereum_tokens(address: Optional[ChecksumEthAddress]) -> Response:
if address is not None:
Expand Down
2 changes: 2 additions & 0 deletions rotkehlchen/api/server.py
Expand Up @@ -24,6 +24,7 @@
AllBalancesResource,
AssetIconsResource,
AssetMovementsResource,
AssetsReplaceResource,
AssetsTypesResource,
AssetUpdatesResource,
AsyncTasksResource,
Expand Down Expand Up @@ -193,6 +194,7 @@
('/blockchains/BTC/xpub', BTCXpubResource),
('/assets', OwnedAssetsResource),
('/assets/types', AssetsTypesResource),
('/assets/replace', AssetsReplaceResource),
('/assets/all', AllAssetsResource),
('/assets/ethereum', EthereumAssetsResource),
('/assets/prices/current', CurrentAssetsPriceResource),
Expand Down
5 changes: 5 additions & 0 deletions rotkehlchen/api/v1/encoding.py
Expand Up @@ -1614,6 +1614,11 @@ class ModifyAssetSchema(Schema):
token = fields.Nested(AssetSchema, required=True)


class AssetsReplaceSchema(Schema):
source_identifier = fields.String(required=True)
target_asset = AssetField(required=True, form_with_incomplete_data=True)


class QueriedAddressesSchema(Schema):
module = fields.String(
required=True,
Expand Down
10 changes: 10 additions & 0 deletions rotkehlchen/api/v1/resources.py
Expand Up @@ -20,6 +20,7 @@
AssetIconUploadSchema,
AssetSchema,
AssetSchemaWithIdentifier,
AssetsReplaceSchema,
AssetUpdatesRequestSchema,
AsyncHistoricalQuerySchema,
AsyncQueryArgumentSchema,
Expand Down Expand Up @@ -409,6 +410,15 @@ def get(self) -> Response:
return self.rest_api.get_asset_types()


class AssetsReplaceResource(BaseResource):

put_schema = AssetsReplaceSchema()

@use_kwargs(put_schema, location='json')
def put(self, source_identifier: str, target_asset: Asset) -> Response:
return self.rest_api.replace_asset(source_identifier, target_asset)


class EthereumAssetsResource(BaseResource):

get_schema = OptionalEthereumAddressSchema()
Expand Down
31 changes: 31 additions & 0 deletions rotkehlchen/db/dbhandler.py
Expand Up @@ -3282,6 +3282,37 @@ def delete_asset_identifier(self, asset_id: str) -> None:
self.conn.commit()
self.update_last_write()

def replace_asset_identifier(self, source_identifier: str, target_asset: Asset) -> None:
"""Replaces a given source identifier either both in the global or the local
user DB with another given asset.
May raise:
- UnknownAsset if the source_identifier can be found nowhere
- InputError if it's not possible to perform the replacement for some reason
"""
globaldb = GlobalDBHandler()
globaldb_data = globaldb.get_asset_data(identifier=source_identifier, form_with_incomplete_data=True) # noqa: E501

cursor = self.conn.cursor()
userdb_query = cursor.execute(
'SELECT COUNT(*) FROM assets WHERE identifier=?;', source_identifier,
).fetchone()

if userdb_query == 0 and globaldb_data is None:
raise UnknownAsset(source_identifier)

if globaldb_data is not None:
globaldb.delete_asset_by_identifer(source_identifier, globaldb_data.asset_type)

if userdb_query != 0:
cursor.execute(
'UPDATE assets SET identifier=? WHERE identifier=?;',
source_identifier, target_asset.identifier,
)

self.conn.commit()
self.update_last_write()

def get_latest_location_value_distribution(self) -> List[LocationData]:
"""Gets the latest location data
Expand Down
27 changes: 27 additions & 0 deletions rotkehlchen/globaldb/handler.py
Expand Up @@ -870,6 +870,33 @@ def add_user_owned_assets(assets: List['Asset']) -> None:

connection.commit()

@staticmethod
def delete_asset_by_identifer(identifier: str, asset_type: AssetType) -> None:
"""Delete an asset by identifier EVEN if it's in the owned assets table
May raise InputError if there is a foreign key relation such as the asset
is a swapped_for or forked_for of another asset.
"""
globaldb = GlobalDBHandler()
connection = globaldb._conn
cursor = connection.cursor()
cursor.execute('DELETE FROM user_owned_assets WHERE asset_id=?;', (identifier,))
cursor.execute(
'DELETE FROM price_history WHERE from_asset=? OR to_asset=? ;',
(identifier, identifier),
)

try:
if asset_type == AssetType.ETHEREUM_TOKEN:
globaldb.delete_ethereum_token(identifier[6:]) # type: ignore
else:
globaldb.delete_custom_asset(identifier)
except InputError:
connection.rollback()
raise

connection.commit()

@staticmethod
def get_assets_with_symbol(symbol: str, asset_type: Optional[AssetType] = None) -> List[AssetData]: # noqa: E501
"""Find all asset entries that have the given symbol"""
Expand Down

0 comments on commit 9f3a129

Please sign in to comment.