Repository: rotki/rotki

Issue:
First time premium user, without synced DB getting 500 Error at account creation.
## Problem Definition

A first time premium user did the following.

1. Bought a premium subscription
2. Got the api keys
3. Created their local rotki account and at the very beginning of the account creation got a 500 server error.

I was able to reproduce on my side. The requirements are. New account, new premium api keys, or generally api keys with no saved DB data on the rotki server. Then you get the following error in the logs.

```
[07/10/2020 10:06:45 CEST] ERROR rotkehlchen.api.server: Exception on /api/1/users [PUT]
Traceback (most recent call last):
  File "/home/lefteris/.virtualenvs/rotkipy37/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/lefteris/.virtualenvs/rotkipy37/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/lefteris/.virtualenvs/rotkipy37/lib/python3.7/site-packages/flask_restful/__init__.py", line 458, in wrapper
    resp = resource(*args, **kwargs)
  File "/home/lefteris/.virtualenvs/rotkipy37/lib/python3.7/site-packages/flask/views.py", line 89, in view
    return self.dispatch_request(*args, **kwargs)
  File "/home/lefteris/.virtualenvs/rotkipy37/lib/python3.7/site-packages/flask_restful/__init__.py", line 573, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/home/lefteris/.virtualenvs/rotkipy37/lib/python3.7/site-packages/webargs/core.py", line 366, in wrapper
    return func(*args, **kwargs)
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/api/v1/resources.py", line 511, in put
    initial_settings=initial_settings,
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/api/rest.py", line 812, in create_new_user
    initial_settings=initial_settings,
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/rotkehlchen.py", line 194, in unlock_user
    sync_approval=sync_approval,
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/premium/sync.py", line 267, in try_premium_at_start
    if self._sync_data_from_server_and_replace_local():
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/premium/sync.py", line 128, in _sync_data_from_server_and_replace_local
    self.data.decompress_and_decrypt_db(self.password, result['data'])
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/data_handler.py", line 229, in decompress_and_decrypt_db
    decrypted_data = decrypt(password.encode(), encrypted_data)
  File "/home/lefteris/w/rotkehlchen/rotkehlchen/crypto.py", line 34, in decrypt
    assert isinstance(given_source, str), 'source should be given in string'
AssertionError: source should be given in string
[07/10/2020 10:06:45 CEST] INFO rotkehlchen.api.server.pywsgi: 127.0.0.1 - - "PUT /api/1/users HTTP/1.1" 500 216 0.627664
```

## Task

This is a very bad thing to see as the first thing when you get start rotki. Fix it.


Discussion:

Resolving patch:
diff --git a/rotkehlchen/premium/premium.py b/rotkehlchen/premium/premium.py
--- a/rotkehlchen/premium/premium.py
+++ b/rotkehlchen/premium/premium.py
@@ -228,6 +228,8 @@ def upload_data(
     def pull_data(self) -> Dict:
         """Pulls data from the server and returns the response dict
 
+        Returns None if there is no DB saved in the server.
+
         Raises RemoteError if there are problems reaching the server or if
         there is an error returned by the server
         """
diff --git a/rotkehlchen/premium/sync.py b/rotkehlchen/premium/sync.py
--- a/rotkehlchen/premium/sync.py
+++ b/rotkehlchen/premium/sync.py
@@ -124,6 +124,10 @@ def _sync_data_from_server_and_replace_local(self) -> bool:
             log.debug('sync from server -- pulling failed.', error=str(e))
             return False
 
+        if result['data'] is None:
+            log.debug('sync from server -- no data found.')
+            return False
+
         try:
             self.data.decompress_and_decrypt_db(self.password, result['data'])
         except UnableToDecryptRemoteData:


Test patch:
diff --git a/rotkehlchen/tests/integration/test_premium.py b/rotkehlchen/tests/integration/test_premium.py
--- a/rotkehlchen/tests/integration/test_premium.py
+++ b/rotkehlchen/tests/integration/test_premium.py
@@ -327,6 +327,29 @@ def test_try_premium_at_start_new_account_different_password_than_remote_db(
         )
 
 
+@pytest.mark.parametrize('start_with_valid_premium', [True])
+def test_try_premium_at_start_first_time_no_previous_db(
+        rotkehlchen_instance,
+        username,
+        db_password,
+        rotki_premium_credentials,
+):
+    """Regression test for https://github.com/rotki/rotki/issues/1571"""
+    setup_starting_environment(
+        rotkehlchen_instance=rotkehlchen_instance,
+        username=username,
+        db_password=db_password,
+        premium_credentials=rotki_premium_credentials,
+        first_time=True,
+        same_hash_with_remote=False,
+        newer_remote_db=False,
+        db_can_sync_setting=True,
+        remote_data=None,
+    )
+    # DB should not have changed and no exception raised
+    assert rotkehlchen_instance.data.db.get_main_currency() == DEFAULT_TESTS_MAIN_CURRENCY
+
+
 def test_premium_credentials():
     """Test the premium credentials class"""
     # Test that improperly formatted keys are spotted
diff --git a/rotkehlchen/tests/utils/premium.py b/rotkehlchen/tests/utils/premium.py
--- a/rotkehlchen/tests/utils/premium.py
+++ b/rotkehlchen/tests/utils/premium.py
@@ -1,4 +1,5 @@
 import base64
+import json
 import os
 from http import HTTPStatus
 from typing import Optional
@@ -47,7 +48,8 @@ def do_mock_get_saved_data(url, data, timeout):  # pylint: disable=unused-argume
         assert len(data) == 1
         assert 'nonce' in data
         assert timeout == ROTKEHLCHEN_SERVER_TIMEOUT
-        payload = f'{{"data": "{saved_data.decode()}"}}'
+        decoded_data = None if saved_data is None else saved_data.decode()
+        payload = f'{{"data": {json.dumps(decoded_data)}}}'
         return MockResponse(200, payload)
 
     return do_mock_get_saved_data
@@ -79,7 +81,6 @@ def mocked_get(url, *args, **kwargs):
                 data_size=metadata_data_size,
             )
         elif 'get_saved_data' in url:
-            assert saved_data is not None
             implementation = mock_get_saved_data(saved_data=saved_data)
         else:
             raise ValueError('Unmocked url in session get for premium')