Repository: rotki/rotki

Issue:
Handle invalid ENS domains at rotki
From discord chat windrush reported that adding `fl00_id.loopring.eth` raises a 500 eror on the APP. I could replicate this.
The logs say

```
[08/05/2021 19:15:41 CEST] ERROR rotkehlchen.api.server: Exception on /api/1/blockchains/ETH [PUT]
Traceback (most recent call last):
  File "rotkehlchen/api/v1/encoding.py", line 1364, in _transform_eth_address
  File "site-packages/eth_utils/address.py", line 110, in to_checksum_address
  File "site-packages/eth_utils/address.py", line 55, in to_normalized_address
  File "site-packages/eth_utils/conversions.py", line 160, in hexstr_if_str
ValueError: when sending a str, it must be a hex string. Got: 'fl00_id.loopring.eth'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "site-packages/idna/core.py", line 336, in uts46_remap
IndexError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "site-packages/ens/utils.py", line 115, in normalize_name
  File "site-packages/idna/core.py", line 341, in uts46_remap
idna.core.InvalidCodepoint: Codepoint U+005F not allowed at position 5 in 'fl00_id.loopring.eth'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "site-packages/flask/app.py", line 1949, in full_dispatch_request
  File "site-packages/flask/app.py", line 1935, in dispatch_request
  File "site-packages/flask_restful/__init__.py", line 468, in wrapper
  File "site-packages/flask/views.py", line 89, in view
  File "site-packages/flask_restful/__init__.py", line 583, in dispatch_request
  File "rotkehlchen/api/v1/parser.py", line 59, in wrapper
  File "rotkehlchen/api/v1/parser.py", line 98, in parse
  File "site-packages/marshmallow/schema.py", line 714, in load
  File "site-packages/marshmallow/schema.py", line 885, in _do_load
  File "site-packages/marshmallow/schema.py", line 1085, in _invoke_load_processors
  File "site-packages/marshmallow/schema.py", line 1216, in _invoke_processors
  File "rotkehlchen/api/v1/encoding.py", line 1458, in transform_data
  File "rotkehlchen/api/v1/encoding.py", line 1368, in _transform_eth_address
  File "rotkehlchen/chain/ethereum/manager.py", line 564, in ens_lookup
  File "rotkehlchen/chain/ethereum/manager.py", line 395, in query
  File "rotkehlchen/chain/ethereum/manager.py", line 608, in _ens_lookup
  File "site-packages/ens/utils.py", line 117, in normalize_name
ens.exceptions.InvalidName: fl00_id.loopring.eth is an invalid name, because Codepoint U+005F not allowed at position 5 in 'fl00_id.loopring.eth'
```



Discussion:

Resolving patch:
diff --git a/rotkehlchen/api/v1/encoding.py b/rotkehlchen/api/v1/encoding.py
--- a/rotkehlchen/api/v1/encoding.py
+++ b/rotkehlchen/api/v1/encoding.py
@@ -31,7 +31,14 @@
 )
 from rotkehlchen.constants.misc import ZERO
 from rotkehlchen.db.settings import ModifiableDBSettings
-from rotkehlchen.errors import DeserializationError, EncodingError, UnknownAsset, XPUBError
+from rotkehlchen.errors import (
+    DeserializationError,
+    EncodingError,
+    InputError,
+    RemoteError,
+    UnknownAsset,
+    XPUBError,
+)
 from rotkehlchen.exchanges.kraken import KrakenAccountType
 from rotkehlchen.exchanges.manager import ALL_SUPPORTED_EXCHANGES, SUPPORTED_EXCHANGES
 from rotkehlchen.fval import FVal
@@ -1334,10 +1341,18 @@ def _transform_btc_address(
     if not given_address.endswith('.eth'):
         return BTCAddress(given_address)
 
-    resolved_address = ethereum.ens_lookup(
-        given_address,
-        blockchain=SupportedBlockchain.BITCOIN,
-    )
+    try:
+        resolved_address = ethereum.ens_lookup(
+            given_address,
+            blockchain=SupportedBlockchain.BITCOIN,
+        )
+    except (RemoteError, InputError) as e:
+        raise ValidationError(
+            f'Given ENS address {given_address} could not be resolved '
+            f'for Bitcoin due to: {str(e)}',
+            field_name='address',
+        ) from None
+
     if resolved_address is None:
         raise ValidationError(
             f'Given ENS address {given_address} could not be resolved for Bitcoin',
@@ -1365,10 +1380,18 @@ def _transform_eth_address(
     except ValueError:
         # Validation will only let .eth names come here.
         # So let's see if it resolves to anything
-        resolved_address = ethereum.ens_lookup(given_address)
+        try:
+            resolved_address = ethereum.ens_lookup(given_address)
+        except (RemoteError, InputError) as e:
+            raise ValidationError(
+                f'Given ENS address {given_address} could not be resolved for Ethereum'
+                f' due to: {str(e)}',
+                field_name='address',
+            ) from None
+
         if resolved_address is None:
             raise ValidationError(
-                f'Given ENS address {given_address} could not be resolved',
+                f'Given ENS address {given_address} could not be resolved for Ethereum',
                 field_name='address',
             ) from None
 
@@ -1399,10 +1422,18 @@ def _transform_ksm_address(
     if not given_address.endswith('.eth'):
         return KusamaAddress(given_address)
 
-    resolved_address = ethereum.ens_lookup(
-        given_address,
-        blockchain=SupportedBlockchain.KUSAMA,
-    )
+    try:
+        resolved_address = ethereum.ens_lookup(
+            given_address,
+            blockchain=SupportedBlockchain.KUSAMA,
+        )
+    except (RemoteError, InputError) as e:
+        raise ValidationError(
+            f'Given ENS address {given_address} could not be resolved '
+            f'for Kusama due to: {str(e)}',
+            field_name='address',
+        ) from None
+
     if resolved_address is None:
         raise ValidationError(
             f'Given ENS address {given_address} could not be resolved for Kusama',
diff --git a/rotkehlchen/chain/ethereum/manager.py b/rotkehlchen/chain/ethereum/manager.py
--- a/rotkehlchen/chain/ethereum/manager.py
+++ b/rotkehlchen/chain/ethereum/manager.py
@@ -7,6 +7,7 @@
 import requests
 from ens import ENS
 from ens.abis import ENS as ENS_ABI, RESOLVER as ENS_RESOLVER_ABI
+from ens.exceptions import InvalidName
 from ens.main import ENS_MAINNET_ADDR
 from ens.utils import is_none_or_zero_address, normal_name_to_hash, normalize_name
 from eth_typing import BlockNumber, HexStr
@@ -29,6 +30,7 @@
 from rotkehlchen.errors import (
     BlockchainQueryError,
     DeserializationError,
+    InputError,
     RemoteError,
     UnableToDecryptRemoteData,
 )
@@ -604,8 +606,13 @@ def _ens_lookup(
         May raise:
         - RemoteError if Etherscan is used and there is a problem querying it or
         parsing its response
+        - InputError if the given name is not a valid ENS name
         """
-        normal_name = normalize_name(name)
+        try:
+            normal_name = normalize_name(name)
+        except InvalidName as e:
+            raise InputError(str(e)) from e
+
         resolver_addr = self._call_contract(
             web3=web3,
             contract_address=ENS_MAINNET_ADDR,


Test patch:
diff --git a/rotkehlchen/tests/unit/test_ens.py b/rotkehlchen/tests/unit/test_ens.py
--- a/rotkehlchen/tests/unit/test_ens.py
+++ b/rotkehlchen/tests/unit/test_ens.py
@@ -3,6 +3,7 @@
 import pytest
 
 from rotkehlchen.chain.ethereum.defi.zerionsdk import ZERION_ADAPTER_ADDRESS
+from rotkehlchen.errors import InputError
 from rotkehlchen.tests.utils.ens import (
     ENS_BRUNO,
     ENS_BRUNO_BTC_BYTES,
@@ -27,6 +28,12 @@ def test_ens_lookup(ethereum_manager, call_order, ethereum_manager_connect_at_st
 
     result = ethereum_manager.ens_lookup('rotki.eth', call_order=call_order)
     assert result == '0x9531C059098e3d194fF87FebB587aB07B30B1306'
+
+    # Test invalid name
+    with pytest.raises(InputError) as e:
+        ethereum_manager.ens_lookup('fl00_id.loopring.eth', call_order=call_order)
+    assert "fl00_id.loopring.eth is an invalid name, because Codepoint U+005F not allowed at position 5 in 'fl00_id.loopring.eth'" in str(e.value)  # noqa: E501
+
     result = ethereum_manager.ens_lookup('ishouldprobablynotexist.eth', call_order=call_order)
     assert result is None