22
33import asyncio
44import logging
5+ import random
56from dataclasses import dataclass
67from typing import Any , Dict , List , Optional , Union
78
89import aiohttp
910
11+ from config .defaults import MetricsServiceConfig
12+
1013
1114@dataclass
1215class BlockchainData :
1316 """Container for blockchain state data."""
1417
1518 block_id : str
1619 transaction_id : str
20+ old_block_id : str = ""
1721
1822
1923class BlockchainDataFetcher :
@@ -54,49 +58,58 @@ async def _make_rpc_request(
5458 self ._logger .error (f"All { self ._max_retries } attempts failed" )
5559 raise
5660
57- async def _fetch_evm_data (self ) -> BlockchainData :
61+ async def _fetch_evm_data (self , blockchain : str ) -> BlockchainData :
5862 try :
59- block = await self ._make_rpc_request (
63+ latest_block = await self ._make_rpc_request (
6064 "eth_getBlockByNumber" , ["latest" , True ]
6165 )
62- if not isinstance (block , dict ):
63- self ._logger .error (f"Invalid block format: { type (block )} " )
64- return BlockchainData (block_id = "" , transaction_id = "" )
6566
66- block_hash = block .get ("hash" , "" )
67+ latest_number = int (latest_block ["number" ], 16 )
68+ offset_range = MetricsServiceConfig .BLOCK_OFFSET_RANGES .get (
69+ blockchain .lower (), (20 , 100 )
70+ )
71+ offset = random .randint (offset_range [0 ], offset_range [1 ])
72+ old_number = max (0 , latest_number - offset )
73+
74+ if not isinstance (latest_block , dict ):
75+ return BlockchainData (block_id = "" , transaction_id = "" , old_block_id = "" )
76+
6777 tx_hash = ""
6878
69- transactions = block .get ("transactions" , [])
79+ transactions = latest_block .get ("transactions" , [])
7080 if transactions and isinstance (transactions [0 ], (dict , str )):
7181 tx_hash = (
7282 transactions [0 ].get ("hash" , "" )
7383 if isinstance (transactions [0 ], dict )
7484 else transactions [0 ]
7585 )
7686
77- # self._logger.info(f"{block_hash} {tx_hash}")
78- return BlockchainData (block_id = block_hash , transaction_id = tx_hash )
87+ return BlockchainData (
88+ block_id = latest_block ["number" ],
89+ transaction_id = tx_hash ,
90+ old_block_id = hex (old_number ),
91+ )
7992
8093 except Exception as e :
8194 self ._logger .error (f"EVM fetch failed: { e !s} " )
82- return BlockchainData (block_id = "" , transaction_id = "" )
95+ return BlockchainData (block_id = "" , transaction_id = "" , old_block_id = "" )
8396
8497 async def _fetch_solana_data (self ) -> BlockchainData :
8598 try :
8699 block_info = await self ._make_rpc_request (
87100 "getLatestBlockhash" , [{"commitment" : "finalized" }]
88101 )
89102 if not isinstance (block_info , dict ):
90- return BlockchainData (block_id = "" , transaction_id = "" )
103+ return BlockchainData (block_id = "" , transaction_id = "" , old_block_id = "" )
91104
92- block_slot = block_info .get ("context" , {}).get ("slot" , "" )
93- if not block_slot :
94- return BlockchainData (block_id = "" , transaction_id = "" )
105+ latest_slot = block_info .get ("context" , {}).get ("slot" , "" )
106+ if not latest_slot :
107+ return BlockchainData (block_id = "" , transaction_id = "" , old_block_id = "" )
95108
96- block = await self ._make_rpc_request (
109+ latest_block = await self ._make_rpc_request (
97110 "getBlock" ,
98111 [
99- block_slot ,
112+ latest_slot ,
100113 {
101114 "encoding" : "json" ,
102115 "maxSupportedTransactionVersion" : 0 ,
@@ -107,17 +120,49 @@ async def _fetch_solana_data(self) -> BlockchainData:
107120 )
108121
109122 tx_sig = ""
110- if isinstance (block , dict ):
111- signatures = block .get ("signatures" , [])
123+ if isinstance (latest_block , dict ):
124+ signatures = latest_block .get ("signatures" , [])
112125 if signatures :
113126 tx_sig = signatures [0 ]
114127
115- # self._logger.info(f"{block_slot} {tx_sig}")
116- return BlockchainData (block_id = str (block_slot ), transaction_id = tx_sig )
128+ offset_range = MetricsServiceConfig .BLOCK_OFFSET_RANGES .get (
129+ "solana" , (100 , 1000 )
130+ )
131+ offset = random .randint (offset_range [0 ], offset_range [1 ])
132+ target_slot = max (0 , latest_slot - offset )
133+ old_slot = None
134+
135+ for slot in range (target_slot - 100 , target_slot ):
136+ try :
137+ block_exists = await self ._make_rpc_request (
138+ "getBlock" ,
139+ [
140+ slot ,
141+ {
142+ "encoding" : "json" ,
143+ "maxSupportedTransactionVersion" : 0 ,
144+ "transactionDetails" : "none" ,
145+ "rewards" : False ,
146+ },
147+ ],
148+ )
149+ if block_exists :
150+ old_slot = slot
151+ break
152+ except Exception as e :
153+ if "Block not available" not in str (e ):
154+ self ._logger .warning (f"Error checking slot { slot } : { e } " )
155+ continue
156+
157+ return BlockchainData (
158+ block_id = str (latest_slot ),
159+ transaction_id = tx_sig ,
160+ old_block_id = str (old_slot ) if old_slot is not None else "" ,
161+ )
117162
118163 except Exception as e :
119164 self ._logger .error (f"Solana fetch failed: { e !s} " )
120- return BlockchainData (block_id = "" , transaction_id = "" )
165+ return BlockchainData (block_id = "" , transaction_id = "" , old_block_id = "" )
121166
122167 async def _fetch_ton_data (self ) -> BlockchainData :
123168 try :
@@ -129,9 +174,16 @@ async def _fetch_ton_data(self) -> BlockchainData:
129174 if not isinstance (last_block , dict ):
130175 raise ValueError ("Invalid last block format" )
131176
132- block_id = (
177+ offset_range = MetricsServiceConfig .BLOCK_OFFSET_RANGES .get ("ton" , (10 , 50 ))
178+ offset = random .randint (offset_range [0 ], offset_range [1 ])
179+ old_seqno = max (0 , last_block ["seqno" ] - offset )
180+
181+ latest_block_id = (
133182 f"{ last_block ['workchain' ]} :{ last_block ['shard' ]} :{ last_block ['seqno' ]} "
134183 )
184+ old_block_id = (
185+ f"{ last_block ['workchain' ]} :{ last_block ['shard' ]} :{ old_seqno } "
186+ )
135187
136188 block = await self ._make_rpc_request (
137189 "getBlockTransactions" ,
@@ -147,17 +199,20 @@ async def _fetch_ton_data(self) -> BlockchainData:
147199 if isinstance (block , dict ) and block .get ("transactions" ):
148200 tx_id = block ["transactions" ][0 ].get ("hash" , "" )
149201
150- # self._logger.info(f"{block_id} {tx_id}")
151- return BlockchainData (block_id = block_id , transaction_id = tx_id )
202+ return BlockchainData (
203+ block_id = latest_block_id ,
204+ transaction_id = tx_id ,
205+ old_block_id = old_block_id ,
206+ )
152207
153208 except Exception as e :
154209 self ._logger .error (f"TON fetch failed: { e !s} " )
155- return BlockchainData (block_id = "" , transaction_id = "" )
210+ return BlockchainData (block_id = "" , transaction_id = "" , old_block_id = "" )
156211
157212 async def fetch_latest_data (self , blockchain : str ) -> BlockchainData :
158213 try :
159214 if blockchain in ("ethereum" , "base" ):
160- return await self ._fetch_evm_data ()
215+ return await self ._fetch_evm_data (blockchain )
161216 elif blockchain == "solana" :
162217 return await self ._fetch_solana_data ()
163218 elif blockchain == "ton" :
0 commit comments