Repository: rotki/rotki

Issue:
When you have a manual balance + a blockchain balance, /api/1/balances reports infinitely growing money
## Problem Definition

I'm writing a simple tool - https://github.com/Zorlin/rotki-balancer - which polls the API for balances.

Weirdly, every time I run it my total value increase by tens of thousands of dollars in the script, despite the script having no memory.

Adding ANY sort of item to Blockchain Balances consistently causes the API to return higher and higher values for anything in Manual Balances.

## Logs

Will provide via DMs.

### System Description
Operating system: Ubuntu 22.04 (amd64) + macOS (Apple Silicon)
Rotki version: 1.27.1



Discussion:
I've since purged my entire Rotki database and I will re-add assets one by one until I find what triggers this bug.
...literally just adding ADA as a manual asset and then curling that endpoint:

curl -s http://localhost:4242/api/1/balances | jq '.result.net_usd'

results in an infinitely growing balance...

:/
Hmm, however, *truly* purging absolutely everything then starting fresh does NOT trigger this when you add Cardano...

EDIT: Okay, we have a way of reproducing this now.

From a fresh installation of Rotki, whether or not you have Premium, simply add a manual balance of Cardano, then add "zorlin.eth" as an Ethereum wallet to scan. Something about that wallet causes an infinitely growing balance in the API.

Update: Also works for "littleweird.eth"... and 0x818B3e11b7E5d051F33afC50B070e686aA13c9B7...
Okay, even more bizarrely, simply adding a Bitcoin wallet via xpub causes this bug too. It looks like ANY wallet added in Blockchain Balances causes the API to wig out :'(
Revised the original post to remove all the noise, having talked to the Rotki team.

Resolving patch:
diff --git a/rotkehlchen/rotkehlchen.py b/rotkehlchen/rotkehlchen.py
--- a/rotkehlchen/rotkehlchen.py
+++ b/rotkehlchen/rotkehlchen.py
@@ -853,10 +853,10 @@ def query_balances(
             blockchain_result = self.chains_aggregator.query_balances(
                 blockchain=None,
                 ignore_cache=ignore_cache,
-            )
+            )  # copies below since if cache is used we end up modifying the balance sheet object
             if len(blockchain_result.totals.assets) != 0:
-                balances[str(Location.BLOCKCHAIN)] = blockchain_result.totals.assets
-            liabilities = blockchain_result.totals.liabilities
+                balances[str(Location.BLOCKCHAIN)] = blockchain_result.totals.assets.copy()
+            liabilities = blockchain_result.totals.liabilities.copy()
         except (RemoteError, EthSyncError) as e:
             problem_free = False
             liabilities = {}


Test patch:
diff --git a/rotkehlchen/tests/api/test_balances.py b/rotkehlchen/tests/api/test_balances.py
--- a/rotkehlchen/tests/api/test_balances.py
+++ b/rotkehlchen/tests/api/test_balances.py
@@ -381,10 +381,13 @@ def test_query_all_balances_with_manually_tracked_balances(
         btc_accounts,
         manually_tracked_balances,
 ):
-    """Test that using the query all balances endpoint also includes manually tracked balances"""
-    # Disable caching of query results
+    """Test that using the query all balances endpoint also includes manually tracked balances
+
+    This test allows caching of results as is default in production and makes sure
+    that result is the same after queryign balances twice and cache is hit. Serves
+    as a regression test for https://github.com/rotki/rotki/issues/5847
+    """
     rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen
-    rotki.chains_aggregator.cache_ttl_secs = 0
     manually_tracked_balances = [ManuallyTrackedBalance(
         id=-1,
         asset=A_BTC,
@@ -432,8 +435,23 @@ def test_query_all_balances_with_manually_tracked_balances(
         btc_accounts=btc_accounts,
         manually_tracked_balances=manually_tracked_balances,
     )
-    # now do the same but save the data in the DB and test it works
-    # `save_data` is False by default but data will save since this is a fresh account
+    # query all balances first time and see manual balances are also there
+    with ExitStack() as stack:
+        setup.enter_all_patches(stack)
+        response = requests.get(
+            api_url_for(
+                rotkehlchen_api_server_with_exchanges,
+                'allbalancesresource',
+            ),
+        )
+    result = assert_proper_response_with_result(response)
+    assert_all_balances(
+        result=result,
+        db=rotki.data.db,
+        expected_data_in_db=True,
+        setup=setup,
+    )
+    # query again, hit cache and check result. Test for https://github.com/rotki/rotki/issues/5847
     with ExitStack() as stack:
         setup.enter_all_patches(stack)
         response = requests.get(