Repository: rotki/rotki

Issue:
Adding/removing blockchain accounts unnecessarily makes an extra query of all balances
## Problem Definition

Due to these two bugs (doing `str(blockchain)`):

https://github.com/rotki/rotki/blob/13f2149bccf0ba6d90f663336c825bbbcc303bb9/rotkehlchen/blockchain.py#L447-L450

https://github.com/rotki/rotki/blob/13f2149bccf0ba6d90f663336c825bbbcc303bb9/rotkehlchen/blockchain.py#L493-L515

In every addition and removal of a blockchain account if there has not been any query (like in the tests or if you just run the backend) of any balances then we have to do a query at the beginning (for addition) and at the end for removal.

This logic is not needed in normal runs with the electron app since it does not let you add/remove anything before the first balance.

The problem with the code above is that the condition is wrong so it **always** does the extra heavy queries.

## Task

Fix it


Discussion:

Resolving patch:
diff --git a/rotkehlchen/blockchain.py b/rotkehlchen/blockchain.py
--- a/rotkehlchen/blockchain.py
+++ b/rotkehlchen/blockchain.py
@@ -10,6 +10,7 @@
 
 from rotkehlchen.assets.asset import Asset, EthereumToken
 from rotkehlchen.constants.assets import A_BTC, A_ETH
+from rotkehlchen.constants.misc import ZERO
 from rotkehlchen.db.utils import BlockchainAccounts
 from rotkehlchen.errors import (
     EthSyncError,
@@ -27,6 +28,7 @@
     ChecksumEthAddress,
     EthAddress,
     ListOfBlockchainAddresses,
+    Price,
     SupportedBlockchain,
 )
 from rotkehlchen.user_messages import MessagesAggregator
@@ -390,8 +392,11 @@ def modify_eth_account(
             )
 
         for token in self.owned_eth_tokens:
-            usd_price = Inquirer().find_usd_price(token)
-            if usd_price == 0:
+            try:
+                usd_price = Inquirer().find_usd_price(token)
+            except RemoteError:
+                usd_price = Price(ZERO)
+            if usd_price == ZERO:
                 # skip tokens that have no price
                 continue
 
@@ -415,11 +420,11 @@ def modify_eth_account(
 
             self.totals[token] = {
                 'amount': add_or_sub(
-                    self.totals[token].get('amount', FVal(0)),
+                    self.totals[token].get('amount', ZERO),
                     token_balance,
                 ),
                 'usd_value': add_or_sub(
-                    self.totals[token].get('usd_value', FVal(0)),
+                    self.totals[token].get('usd_value', ZERO),
                     usd_value,
                 ),
             }
@@ -446,7 +451,7 @@ def add_blockchain_accounts(
 
         # If no blockchain query has happened before then we need to query the relevant
         # chain to populate the self.balances mapping.
-        if str(blockchain) not in self.balances:
+        if blockchain.value not in self.balances:
             self.query_balances(blockchain, ignore_cache=True)
 
         added_accounts = []
@@ -494,7 +499,7 @@ def remove_blockchain_accounts(
         # chain to populate the self.balances mapping. But query has to happen after
         # account removal so as not to query unneeded accounts
         balances_queried_before = True
-        if str(blockchain) not in self.balances:
+        if blockchain.value not in self.balances:
             balances_queried_before = False
 
         removed_accounts = []
@@ -587,8 +592,11 @@ def query_ethereum_tokens(
         token_balances = {}
         token_usd_price = {}
         for token in tokens:
-            usd_price = Inquirer().find_usd_price(token)
-            if usd_price == 0:
+            try:
+                usd_price = Inquirer().find_usd_price(token)
+            except RemoteError:
+                usd_price = Price(ZERO)
+            if usd_price == ZERO:
                 # skip tokens that have no price
                 continue
             token_usd_price[token] = usd_price
diff --git a/rotkehlchen/exchanges/kraken.py b/rotkehlchen/exchanges/kraken.py
--- a/rotkehlchen/exchanges/kraken.py
+++ b/rotkehlchen/exchanges/kraken.py
@@ -14,6 +14,7 @@
 from rotkehlchen.assets.converters import KRAKEN_TO_WORLD, asset_from_kraken
 from rotkehlchen.constants import KRAKEN_API_VERSION, KRAKEN_BASE_URL
 from rotkehlchen.constants.assets import A_DAI, A_ETH
+from rotkehlchen.constants.misc import ZERO
 from rotkehlchen.errors import (
     DeserializationError,
     RecoverableRequestError,
@@ -414,15 +415,19 @@ def query_balances(self) -> Tuple[Optional[dict], str]:
 
             entry = {}
             entry['amount'] = v
-            try:
-                usd_price = Inquirer().find_usd_price(our_asset)
-            except RemoteError as e:
-                self.msg_aggregator.add_error(
-                    f'Error processing kraken balance entry due to inability to '
-                    f'query USD price: {str(e)}. Skipping balance entry',
-                )
-                continue
-            entry['usd_value'] = FVal(v * usd_price)
+            if k == 'KFEE':
+                # There is no price value for KFEE. TODO: Shouldn't we then just skip the balance?
+                entry['usd_value'] = ZERO
+            else:
+                try:
+                    usd_price = Inquirer().find_usd_price(our_asset)
+                except RemoteError as e:
+                    self.msg_aggregator.add_error(
+                        f'Error processing kraken balance entry due to inability to '
+                        f'query USD price: {str(e)}. Skipping balance entry',
+                    )
+                    continue
+                entry['usd_value'] = FVal(v * usd_price)
 
             balances[our_asset] = entry
             log.debug(


Test patch:
diff --git a/rotkehlchen/tests/test_blockchain.py b/rotkehlchen/tests/test_blockchain.py
--- a/rotkehlchen/tests/test_blockchain.py
+++ b/rotkehlchen/tests/test_blockchain.py
@@ -1,11 +1,13 @@
 import operator
 import os
+from unittest.mock import patch
 
 import pytest
 from eth_utils.address import to_checksum_address
 
 from rotkehlchen.constants.assets import A_BTC
 from rotkehlchen.tests.utils.blockchain import DEFAULT_BALANCE
+from rotkehlchen.tests.utils.constants import A_GNO
 from rotkehlchen.typing import SupportedBlockchain
 from rotkehlchen.utils.misc import from_wei
 
@@ -69,3 +71,49 @@ def test_add_remove_ethereum_account_saved_as_checksummed(blockchain):
     )
 
     assert checksummed_addr not in blockchain.accounts.eth
+
+
+def test_add_remove_account_assure_all_balances_not_always_queried(blockchain):
+    """Due to a programming mistake at addition and removal of blockchain accounts
+    after the first time all balances were queried every time. That slowed
+    everything down (https://github.com/rotki/rotki/issues/678).
+
+    This is a regression test for that behaviour
+    """
+    addr1 = '0xe188c6BEBB81b96A65aa20dDB9e2aef62627fa4c'
+    blockchain.add_blockchain_accounts(
+        blockchain=SupportedBlockchain.ETHEREUM,
+        accounts=[addr1],
+    )
+    assert addr1 in blockchain.accounts.eth
+
+    with patch.object(blockchain, 'query_balances') as mock:
+        blockchain.remove_blockchain_accounts(
+            blockchain=SupportedBlockchain.ETHEREUM,
+            accounts=[addr1],
+        )
+
+    assert addr1 not in blockchain.accounts.eth
+    assert mock.call_count == 0, 'blockchain.query_balances() should not have been called'
+
+    addr2 = '0x78a087fCf440315b843632cFd6FDE6E5adcCc2C2'
+    with patch.object(blockchain, 'query_balances') as mock:
+        blockchain.add_blockchain_accounts(
+            blockchain=SupportedBlockchain.ETHEREUM,
+            accounts=[addr2],
+        )
+
+    assert addr2 in blockchain.accounts.eth
+    assert mock.call_count == 0, 'blockchain.query_balances() should not have been called'
+
+    with patch.object(blockchain, 'query_balances') as mock:
+        blockchain.track_new_tokens(new_tokens=[A_GNO])
+
+    assert mock.call_count == 0, 'blockchain.query_balances() should not have been called'
+    assert A_GNO in blockchain.owned_eth_tokens
+
+    with patch.object(blockchain, 'query_balances') as mock:
+        blockchain.remove_eth_tokens(tokens=[A_GNO])
+
+    assert A_GNO not in blockchain.owned_eth_tokens
+    assert mock.call_count == 0, 'blockchain.query_balances() should not have been called'