22from dataclasses import dataclass
33from typing import Collection , Optional , OrderedDict , Union
44
5- from django .db import IntegrityError , transaction
5+ from django .db import transaction
66from django .db .models import Min , Q
77
8- from eth_typing import ChecksumAddress
8+ from eth_typing import ChecksumAddress , Hash32
99from hexbytes import HexBytes
1010from safe_eth .eth import EthereumClient , get_auto_ethereum_client
1111from safe_eth .util .util import to_0x_hex_str
@@ -107,21 +107,6 @@ def __init__(
107107
108108 self .tx_processor : SafeTxProcessor = SafeTxProcessorProvider ()
109109
110- def block_get_or_create_from_block_hash (self , block_hash : int ):
111- try :
112- return EthereumBlock .objects .get (block_hash = block_hash )
113- except EthereumBlock .DoesNotExist :
114- current_block_number = (
115- self .ethereum_client .current_block_number
116- ) # For reorgs
117- block = self .ethereum_client .get_block (block_hash )
118- confirmed = (
119- current_block_number - block ["number" ]
120- ) >= self .eth_reorg_blocks
121- return EthereumBlock .objects .get_or_create_from_block (
122- block , confirmed = confirmed
123- )
124-
125110 def get_erc20_721_current_indexing_block_number (self ) -> int :
126111 return IndexingStatusDb .objects .get_erc20_721_indexing_status ().block_number
127112
@@ -229,40 +214,56 @@ def is_service_synced(self) -> bool:
229214
230215 return synced
231216
232- def tx_create_or_update_from_tx_hash (self , tx_hash : str ) -> "EthereumTx" :
233- try :
234- ethereum_tx = EthereumTx .objects .get (tx_hash = tx_hash )
235- # For txs stored before being mined
236- if ethereum_tx .block is None :
237- tx_receipt = self .ethereum_client .get_transaction_receipt (tx_hash )
238- ethereum_block = self .block_get_or_create_from_block_hash (
239- tx_receipt ["blockHash" ]
217+ def txs_create_or_update_from_block_hashes (
218+ self , block_hashes : set [Hash32 ]
219+ ) -> tuple [int , dict [Hash32 , EthereumBlock ]]:
220+ block_hashes = list (block_hashes ) # Iterate in a defined order
221+ blocks = self .ethereum_client .get_blocks (block_hashes )
222+
223+ # Validate blocks from RPC
224+ for block_hash , block in zip (block_hashes , blocks ):
225+ if not block :
226+ raise BlockNotFoundException (
227+ f"Block with hash={ block_hash } was not found"
240228 )
241- ethereum_tx . update_with_block_and_receipt ( ethereum_block , tx_receipt )
242- return ethereum_tx
243- except EthereumTx . DoesNotExist :
244- tx_receipt = self . ethereum_client . get_transaction_receipt ( tx_hash )
245- ethereum_block = self .block_get_or_create_from_block_hash (
246- tx_receipt [ "blockHash" ]
247- )
248- tx = self . ethereum_client . get_transaction ( tx_hash )
249- return EthereumTx . objects . create_from_tx_dict (
250- tx , tx_receipt = tx_receipt , ethereum_block = ethereum_block
229+ assert block_hash == to_0x_hex_str (
230+ block [ "hash" ]
231+ ), f" { block_hash } does not match retrieved block hash"
232+
233+ current_block_number = self .ethereum_client . current_block_number
234+ ethereum_blocks_to_insert = [
235+ EthereumBlock . objects . from_block_dict (
236+ block ,
237+ confirmed = ( current_block_number - block [ "number" ])
238+ >= self . eth_reorg_blocks ,
251239 )
240+ for block in blocks
241+ ]
242+ inserted = EthereumBlock .objects .bulk_create_from_generator (
243+ iter (ethereum_blocks_to_insert ), ignore_conflicts = True
244+ )
245+ return inserted , {
246+ HexBytes (ethereum_block .block_hash ): ethereum_block
247+ for ethereum_block in ethereum_blocks_to_insert
248+ }
252249
253250 def txs_create_or_update_from_tx_hashes (
254251 self , tx_hashes : Collection [Union [str , bytes ]]
255252 ) -> list ["EthereumTx" ]:
253+ """
254+ :param tx_hashes:
255+ :return: List of EthereumTx in the same order that `tx_hashes` were provided
256+ """
256257 logger .debug ("Don't retrieve existing txs on DB. Find them first" )
257258 # Search first in database
258259 ethereum_txs_dict = OrderedDict .fromkeys (
259- [to_0x_hex_str ( HexBytes (tx_hash ) ) for tx_hash in tx_hashes ]
260+ [HexBytes (tx_hash ) for tx_hash in tx_hashes ]
260261 )
261262 db_ethereum_txs = EthereumTx .objects .filter (tx_hash__in = tx_hashes ).exclude (
262263 block = None
263264 )
264265 for db_ethereum_tx in db_ethereum_txs :
265- ethereum_txs_dict [db_ethereum_tx .tx_hash ] = db_ethereum_tx
266+ ethereum_txs_dict [HexBytes ( db_ethereum_tx .tx_hash ) ] = db_ethereum_tx
266267 logger .debug ("Found %d existing txs on DB" , len (db_ethereum_txs ))
267268
268269 # Retrieve from the node the txs missing from database
@@ -275,7 +276,7 @@ def txs_create_or_update_from_tx_hashes(
275276 if not tx_hashes_not_in_db :
276277 return list (ethereum_txs_dict .values ())
277278
278- # Get receipts for hashes not in db
279+ # Get receipts for hashes not in db. First get the receipts as they guarantee tx is mined and confirmed
279280 logger .debug ("Get tx receipts for hashes not on db" )
280281 tx_receipts = []
281282 for tx_hash , tx_receipt in zip (
@@ -320,50 +321,35 @@ def txs_create_or_update_from_tx_hashes(
320321
321322 block_hashes .add (to_0x_hex_str (tx ["blockHash" ]))
322323 txs .append (tx )
323- logger .debug ("Got txs from RPC. Getting %d blocks" , len (block_hashes ))
324-
325- blocks = self .ethereum_client .get_blocks (block_hashes )
326- block_dict = {}
327- for block_hash , block in zip (block_hashes , blocks ):
328- block = block or self .ethereum_client .get_block (
329- block_hash
330- ) # Retry fetching if failed
331- if not block :
332- raise BlockNotFoundException (
333- f"Block with hash={ block_hash } was not found"
334- )
335- assert block_hash == to_0x_hex_str (block ["hash" ])
336- block_dict [block ["hash" ]] = block
337324
338325 logger .debug (
339- "Got blocks from RPC. Inserting blocks. Creating txs or updating them if they have not receipt"
326+ "Got txs from RPC. Getting and inserting %d blocks" , len (block_hashes )
327+ )
328+ number_inserted_blocks , blocks = self .txs_create_or_update_from_block_hashes (
329+ block_hashes
340330 )
331+ logger .debug ("Inserted %d blocks" , number_inserted_blocks )
332+
333+ logger .debug ("Inserting %d transactions" , len (txs ))
334+ # Create new transactions or ignore if they already exist
335+ ethereum_txs_to_insert = [
336+ EthereumTx .objects .from_tx_dict (tx , tx_receipt )
337+ for tx , tx_receipt in zip (txs , tx_receipts )
338+ ]
339+ number_inserted_txs = EthereumTx .objects .bulk_create_from_generator (
340+ iter (ethereum_txs_to_insert ), ignore_conflicts = True
341+ )
342+ for ethereum_tx , tx in zip (ethereum_txs_to_insert , txs ):
343+ # Trust they were inserted and add them to the txs dictionary
344+ assert ethereum_tx .tx_hash == to_0x_hex_str (
345+ tx ["hash" ]
346+ ), f"{ ethereum_tx .tx_hash } does not match retrieved tx hash"
347+ ethereum_tx .block = blocks [tx ["blockHash" ]]
348+ ethereum_txs_dict [HexBytes (ethereum_tx .tx_hash )] = ethereum_tx
349+ # Block info is required for traces
350+
351+ logger .debug ("Inserted %d transactions" , number_inserted_txs )
341352
342- # Create new transactions or update them if they have no receipt
343- current_block_number = self .ethereum_client .current_block_number
344- for tx , tx_receipt in zip (txs , tx_receipts ):
345- block = block_dict [tx ["blockHash" ]]
346- confirmed = (
347- current_block_number - block ["number" ]
348- ) >= self .eth_reorg_blocks
349- ethereum_block : EthereumBlock = (
350- EthereumBlock .objects .get_or_create_from_block (
351- block , confirmed = confirmed
352- )
353- )
354- try :
355- with transaction .atomic ():
356- ethereum_tx = EthereumTx .objects .create_from_tx_dict (
357- tx , tx_receipt = tx_receipt , ethereum_block = ethereum_block
358- )
359- ethereum_txs_dict [to_0x_hex_str (HexBytes (ethereum_tx .tx_hash ))] = (
360- ethereum_tx
361- )
362- except IntegrityError : # Tx exists
363- ethereum_tx = EthereumTx .objects .get (tx_hash = tx ["hash" ])
364- # For txs stored before being mined
365- ethereum_tx .update_with_block_and_receipt (ethereum_block , tx_receipt )
366- ethereum_txs_dict [ethereum_tx .tx_hash ] = ethereum_tx
367353 logger .debug ("Blocks, transactions and receipts were inserted" )
368354
369355 return list (ethereum_txs_dict .values ())
0 commit comments