88import asyncio
99import logging
1010import struct
11+ from copy import deepcopy
1112from typing import Any , Dict , List , Optional
1213
1314from solana .rpc .async_api import AsyncClient
@@ -67,6 +68,35 @@ async def close(self) -> None:
6768 await self ._client .close ()
6869 self ._client = None
6970
71+ async def health_check (self ) -> bool :
72+ """
73+ Check if the Solana RPC connection is healthy and can fetch blockhashes.
74+
75+ Returns:
76+ True if connection is healthy, False otherwise
77+ """
78+ try :
79+ # Test basic connection by getting epoch info (lightweight check)
80+ epoch_info = await self .client .get_epoch_info (commitment = self .commitment )
81+ if not epoch_info .value :
82+ logger .warning ("Failed to fetch epoch info" )
83+ return False
84+
85+ # Test blockhash fetching (most common failure point)
86+ blockhash_resp = await self .client .get_latest_blockhash (
87+ commitment = self .commitment
88+ )
89+ if not blockhash_resp .value or not blockhash_resp .value .blockhash :
90+ logger .warning ("Failed to fetch latest blockhash" )
91+ return False
92+
93+ logger .debug ("RPC connection health check passed" )
94+ return True
95+
96+ except Exception as e :
97+ logger .warning (f"RPC health check failed: { e } " )
98+ return False
99+
70100 async def __aenter__ (self ) -> "SolanaAIRegistriesClient" :
71101 """Async context manager entry."""
72102 return self
@@ -186,7 +216,7 @@ async def send_transaction(
186216 transaction : Transaction ,
187217 signers : List [Keypair ],
188218 opts : Optional [TxOpts ] = None ,
189- max_retries : int = 3 ,
219+ max_retries : int = 5 ,
190220 ) -> str :
191221 """
192222 Send transaction with error handling and retry logic.
@@ -209,28 +239,55 @@ async def send_transaction(
209239 last_error = None
210240 for attempt in range (max_retries ):
211241 try :
212- # Get recent blockhash
242+ # Get fresh blockhash for each attempt
213243 blockhash_resp = await self .client .get_latest_blockhash (
214244 commitment = self .commitment
215245 )
216246
217- # Sign transaction with recent blockhash
218- transaction .sign (signers , blockhash_resp .value .blockhash )
219-
220- # Send transaction
221- response = await self .client .send_transaction (transaction , opts = opts )
247+ # Wait a bit to ensure blockhash is fully propagated
248+ if attempt > 0 :
249+ await asyncio .sleep (0.5 )
250+
251+ # Create a new transaction instance to avoid signature conflicts
252+ tx_copy = deepcopy (transaction )
253+
254+ # Sign transaction with fresh blockhash
255+ tx_copy .sign (signers , blockhash_resp .value .blockhash )
256+
257+ # Send transaction with additional retry-friendly options
258+ response = await self .client .send_transaction (
259+ tx_copy ,
260+ opts = TxOpts (
261+ skip_confirmation = opts .skip_confirmation ,
262+ # Always run preflight to catch blockhash issues early
263+ skip_preflight = False ,
264+ # Let our outer retry loop handle retries
265+ max_retries = 1 ,
266+ ),
267+ )
222268
223269 signature = str (response .value )
224270 logger .info (f"Transaction sent successfully: { signature } " )
225271 return signature
226272
227273 except Exception as e :
228274 last_error = e
229- logger .warning (
230- f"Transaction attempt { attempt + 1 } /{ max_retries } failed: { e } "
231- )
232- if attempt < max_retries - 1 :
233- await asyncio .sleep (1.0 * (attempt + 1 )) # Exponential backoff
275+ error_msg = str (e ).lower ()
276+
277+ # Check if it's a blockhash-related error
278+ if "blockhash not found" in error_msg or "blockhash" in error_msg :
279+ logger .warning (
280+ f"Blockhash error on attempt { attempt + 1 } /{ max_retries } : { e } "
281+ )
282+ # For blockhash errors, wait longer before retry
283+ if attempt < max_retries - 1 :
284+ await asyncio .sleep (2.0 + (attempt * 1.0 ))
285+ else :
286+ logger .warning (
287+ f"Transaction attempt { attempt + 1 } /{ max_retries } failed: { e } "
288+ )
289+ if attempt < max_retries - 1 :
290+ await asyncio .sleep (1.0 * (attempt + 1 )) # Exponential backoff
234291
235292 # All retries failed
236293 raise TransactionError (
0 commit comments