Repository: rotki/rotki

Issue:
Handle genesis block transactions
## Problem Definition

At the moment when querying etherscan for eth transactions if you try to query an address that was given eth in the genesis block the app will skip the transaction as there is no transaction hash nor nonce.

## Logs

`Failed to turn GENESIS_XXXXXXX to bytes. Skipping transaction`

## Proposed solution by lefteris

Create a `0x0` transaction and create fake internal transactions spreading the ETH to the different addresses.
https://discord.com/channels/657906918408585217/683245846070165504/984099000724627506

### System Description

Operating system: Linux
Rotki version: 1.24.1



Discussion:

Resolving patch:
diff --git a/rotkehlchen/chain/ethereum/constants.py b/rotkehlchen/chain/ethereum/constants.py
--- a/rotkehlchen/chain/ethereum/constants.py
+++ b/rotkehlchen/chain/ethereum/constants.py
@@ -1,4 +1,5 @@
 from rotkehlchen.chain.ethereum.types import string_to_ethereum_address
+from rotkehlchen.types import Timestamp, deserialize_evm_tx_hash
 
 RANGE_PREFIX_ETHTX = 'ethtxs'
 RANGE_PREFIX_ETHINTERNALTX = 'ethinternaltxs'
@@ -9,5 +10,7 @@
 MODULES_PREFIX_LENGTH = len(MODULES_PREFIX)
 
 ZERO_ADDRESS = string_to_ethereum_address('0x0000000000000000000000000000000000000000')
+GENESIS_HASH = deserialize_evm_tx_hash('0x' + '0' * 64)  # hash for transactions in genesis block
+ETHEREUM_BEGIN = Timestamp(1438269973)
 
 CPT_KRAKEN = 'kraken'
diff --git a/rotkehlchen/db/ethtx.py b/rotkehlchen/db/ethtx.py
--- a/rotkehlchen/db/ethtx.py
+++ b/rotkehlchen/db/ethtx.py
@@ -2,9 +2,12 @@
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
 
 from rotkehlchen.chain.ethereum.constants import (
+    ETHEREUM_BEGIN,
+    GENESIS_HASH,
     RANGE_PREFIX_ETHINTERNALTX,
     RANGE_PREFIX_ETHTOKENTX,
     RANGE_PREFIX_ETHTX,
+    ZERO_ADDRESS,
 )
 from rotkehlchen.chain.ethereum.structures import EthereumTxReceipt, EthereumTxReceiptLog
 from rotkehlchen.db.constants import HISTORY_MAPPING_DECODED
@@ -420,3 +423,43 @@ def get_queried_range(self, address: ChecksumEthAddress) -> Tuple[Timestamp, Tim
             ends.append(tx_range[1])
 
         return max(starts), min(ends)
+
+    def get_max_genesis_trace_id(self) -> int:
+        """Get the max trace id of genesis internal transactions from the database.
+        If no internal transactions were found, returns 0 (zero)."""
+        cursor = self.db.conn.cursor()
+        trace_id, = cursor.execute(
+            'SELECT MAX(trace_id) from ethereum_internal_transactions WHERE parent_tx_hash=?',
+            (GENESIS_HASH,),
+        ).fetchone()
+        return trace_id if trace_id is not None else 0
+
+    def get_or_create_genesis_transaction(
+            self,
+            account: ChecksumEthAddress,
+    ) -> EthereumTransaction:
+        tx_in_db = self.get_ethereum_transactions(
+            filter_=ETHTransactionsFilterQuery.make(tx_hash=GENESIS_HASH, addresses=[account]),
+            has_premium=True,
+        )
+        if len(tx_in_db) == 1:
+            tx = tx_in_db[0]
+        else:
+            tx = EthereumTransaction(
+                timestamp=ETHEREUM_BEGIN,
+                block_number=0,
+                tx_hash=GENESIS_HASH,
+                from_address=ZERO_ADDRESS,
+                to_address=None,
+                value=0,
+                gas=0,
+                gas_price=0,
+                gas_used=0,
+                input_data=b'',
+                nonce=0,
+            )
+            self.add_ethereum_transactions(
+                ethereum_transactions=[tx],
+                relevant_address=account,
+            )
+        return tx
diff --git a/rotkehlchen/externalapis/etherscan.py b/rotkehlchen/externalapis/etherscan.py
--- a/rotkehlchen/externalapis/etherscan.py
+++ b/rotkehlchen/externalapis/etherscan.py
@@ -17,12 +17,14 @@
 import gevent
 import requests
 
+from rotkehlchen.chain.ethereum.constants import GENESIS_HASH, ZERO_ADDRESS
 from rotkehlchen.constants.timing import (
     DEFAULT_CONNECT_TIMEOUT,
     DEFAULT_READ_TIMEOUT,
     DEFAULT_TIMEOUT_TUPLE,
 )
 from rotkehlchen.db.dbhandler import DBHandler
+from rotkehlchen.db.ethtx import DBEthTx
 from rotkehlchen.errors.misc import RemoteError
 from rotkehlchen.errors.serialization import DeserializationError
 from rotkehlchen.externalapis.interface import ExternalServiceWithApiKey
@@ -285,11 +287,30 @@ def get_transactions(
             for entry in result:
                 gevent.sleep(0)
                 try:
-                    tx = deserialize_ethereum_transaction(  # type: ignore
-                        data=entry,
-                        internal=is_internal,
-                        ethereum=None,
-                    )
+                    # Handle genesis block transactions
+                    if entry['hash'].startswith('GENESIS') is False:
+                        tx = deserialize_ethereum_transaction(  # type: ignore
+                            data=entry,
+                            internal=is_internal,
+                            ethereum=None,
+                        )
+                    else:
+                        # Handling genesis transactions
+                        dbtx = DBEthTx(self.db)  # type: ignore
+                        tx = dbtx.get_or_create_genesis_transaction(account=account)
+                        trace_id = dbtx.get_max_genesis_trace_id()
+                        entry['from'] = ZERO_ADDRESS
+                        entry['hash'] = GENESIS_HASH
+                        entry['traceId'] = trace_id
+                        internal_tx = deserialize_ethereum_transaction(
+                            data=entry,
+                            internal=True,
+                            ethereum=None,
+                        )
+                        dbtx.add_ethereum_internal_transactions(
+                            transactions=[internal_tx],
+                            relevant_address=account,
+                        )
                 except DeserializationError as e:
                     self.msg_aggregator.add_warning(f'{str(e)}. Skipping transaction')
                     continue


Test patch:
diff --git a/rotkehlchen/tests/external_apis/test_etherscan.py b/rotkehlchen/tests/external_apis/test_etherscan.py
--- a/rotkehlchen/tests/external_apis/test_etherscan.py
+++ b/rotkehlchen/tests/external_apis/test_etherscan.py
@@ -2,15 +2,23 @@
 from unittest.mock import patch
 
 import pytest
+from eth_utils import to_checksum_address
 
+from rotkehlchen.chain.ethereum.constants import ETHEREUM_BEGIN, GENESIS_HASH, ZERO_ADDRESS
 from rotkehlchen.db.dbhandler import DBHandler
+from rotkehlchen.db.ethtx import DBEthTx
+from rotkehlchen.db.filtering import ETHTransactionsFilterQuery
 from rotkehlchen.externalapis.etherscan import Etherscan
 from rotkehlchen.serialization.deserialize import deserialize_ethereum_transaction
 from rotkehlchen.tests.utils.mock import MockResponse
 from rotkehlchen.types import (
+    BlockchainAccountData,
+    EthereumInternalTransaction,
     EthereumTransaction,
     ExternalService,
     ExternalServiceApiCredentials,
+    SupportedBlockchain,
+    Timestamp,
     deserialize_evm_tx_hash,
 )
 
@@ -90,3 +98,66 @@ def test_deserialize_transaction_from_etherscan():
         input_data=bytes.fromhex(data['input'][2:]),
         nonce=0,
     )
+
+
+def test_etherscan_get_transactions_genesis_block(eth_transactions):
+    """Test that the genesis transactions are correctly returned"""
+    account = to_checksum_address('0xC951900c341aBbb3BAfbf7ee2029377071Dbc36A')
+    db = eth_transactions.database
+    db.add_blockchain_accounts(
+        blockchain=SupportedBlockchain.ETHEREUM,
+        account_data=[
+            BlockchainAccountData(address=account),
+        ],
+    )
+    eth_transactions.single_address_query_transactions(
+        address=account,
+        start_ts=ETHEREUM_BEGIN,
+        end_ts=Timestamp(1451606400),
+    )
+    dbtx = DBEthTx(database=db)
+    regular_tx_in_db = dbtx.get_ethereum_transactions(
+        filter_=ETHTransactionsFilterQuery.make(),
+        has_premium=True,
+    )
+    internal_tx_in_db = dbtx.get_ethereum_internal_transactions(parent_tx_hash=GENESIS_HASH)
+
+    assert regular_tx_in_db == [
+        EthereumTransaction(
+            tx_hash=GENESIS_HASH,
+            timestamp=ETHEREUM_BEGIN,
+            block_number=0,
+            from_address=ZERO_ADDRESS,
+            to_address=None,
+            value=0,
+            gas=0,
+            gas_price=0,
+            gas_used=0,
+            input_data=b'',
+            nonce=0,
+        ), EthereumTransaction(
+            tx_hash=deserialize_evm_tx_hash('0x352b93ac19dfbfd65d4d8385cded959d7a156c3f352a71a5a49560b088e1c8df'),  # noqa: E501
+            timestamp=Timestamp(1443534531),
+            block_number=307793,
+            from_address='0xC951900c341aBbb3BAfbf7ee2029377071Dbc36A',
+            to_address='0x2910543Af39abA0Cd09dBb2D50200b3E800A63D2',
+            value=327400000000000000000,
+            gas=50000,
+            gas_price=1171602790622,
+            gas_used=21612,
+            input_data=b'EN06ENDWG',
+            nonce=0,
+        ),
+    ]
+
+    assert internal_tx_in_db == [
+        EthereumInternalTransaction(
+            parent_tx_hash=GENESIS_HASH,
+            trace_id=0,
+            timestamp=ETHEREUM_BEGIN,
+            block_number=0,
+            from_address=ZERO_ADDRESS,
+            to_address='0xC951900c341aBbb3BAfbf7ee2029377071Dbc36A',
+            value='327600000000000000000',
+        ),
+    ]