@@ -66,9 +66,46 @@ def _get_available_rpcs(self) -> List[str]:
6666 return [self .rpc_url ]
6767 return FALLBACK_DEVNET_RPCS
6868
69+ def _parse_blockhash_response (self , response : Any , rpc_url : str ) -> Optional [Hash ]:
70+ """
71+ Parse blockhash response safely, handling various response types.
72+
73+ Args:
74+ response: RPC response object
75+ rpc_url: RPC endpoint URL for logging
76+
77+ Returns:
78+ Hash object if valid blockhash found, None otherwise
79+ """
80+ try :
81+ # Check if response has expected structure
82+ if hasattr (response , "value" ) and response .value :
83+ if hasattr (response .value , "blockhash" ):
84+ blockhash = response .value .blockhash
85+ # Validate that blockhash is a Hash object (not int or other type)
86+ if isinstance (blockhash , Hash ):
87+ return blockhash
88+ else :
89+ logger .warning (
90+ f"Invalid blockhash type from { rpc_url } : "
91+ f"{ type (blockhash )} - expected Hash object"
92+ )
93+ return None
94+ else :
95+ logger .warning (f"Response from { rpc_url } missing blockhash field" )
96+ return None
97+ else :
98+ logger .warning (
99+ f"Invalid response structure from { rpc_url } : { type (response )} "
100+ )
101+ return None
102+ except Exception as e :
103+ logger .warning (f"Error parsing blockhash response from { rpc_url } : { e } " )
104+ return None
105+
69106 async def _get_fresh_blockhash (self , max_attempts : int = 3 ) -> Hash :
70107 """
71- Get a fresh blockhash with retry logic.
108+ Get a fresh blockhash with robust retry logic and RPC failover .
72109
73110 Args:
74111 max_attempts: Maximum number of attempts to fetch blockhash
@@ -93,26 +130,37 @@ async def _get_fresh_blockhash(self, max_attempts: int = 3) -> Hash:
93130 self .rpc_url = rpc_to_try
94131 self ._client = AsyncClient (self .rpc_url , commitment = self .commitment )
95132
96- # Wait a moment for RPC to be ready
133+ # Increased wait time for RPC to be ready and reduce rate limiting
97134 if attempt > 0 :
98- await asyncio .sleep (0.5 + (attempt * 0.5 ))
99-
135+ wait_time = 2.5 + (
136+ attempt * 2.0
137+ ) # Start at 2.5s, increase by 2s each attempt
138+ logger .debug (f"Waiting { wait_time } s before retry { attempt + 1 } " )
139+ await asyncio .sleep (wait_time )
140+
141+ logger .debug (
142+ f"Fetching blockhash from { rpc_to_try } (attempt { attempt + 1 } )"
143+ )
100144 blockhash_resp = await self ._client .get_latest_blockhash (
101145 commitment = self .commitment
102146 )
103147
104- # Handle response type safely - check if response has .value attribute
105- if (
106- hasattr (blockhash_resp , "value" )
107- and blockhash_resp .value
108- and hasattr (blockhash_resp .value , "blockhash" )
109- ):
148+ # Use robust parser to handle response
149+ parsed_blockhash = self ._parse_blockhash_response (
150+ blockhash_resp , rpc_to_try
151+ )
152+ if parsed_blockhash :
110153 logger .debug (f"Fresh blockhash obtained from { rpc_to_try } " )
111- return blockhash_resp .value .blockhash # Return Hash object directly
112- else :
113- raise ConnectionError (
114- f"Blockhash response was invalid: { type (blockhash_resp )} "
115- )
154+ return parsed_blockhash
155+
156+ # If parsing failed, try next RPC
157+ logger .warning (
158+ f"Failed to parse blockhash response from { rpc_to_try } , "
159+ "trying next endpoint"
160+ )
161+ raise ConnectionError (
162+ f"Blockhash response was invalid from { rpc_to_try } "
163+ )
116164
117165 except Exception as e :
118166 logger .warning (
@@ -323,11 +371,15 @@ async def send_transaction(
323371 for attempt in range (max_retries ):
324372 try :
325373 # Get fresh blockhash for each attempt using robust method
326- fresh_blockhash = await self ._get_fresh_blockhash (max_attempts = 3 )
374+ fresh_blockhash = await self ._get_fresh_blockhash (
375+ max_attempts = 3
376+ )
327377
328- # Wait a bit to ensure blockhash is fully propagated
378+ # Wait longer to ensure blockhash propagated across network
329379 if attempt > 0 :
330- await asyncio .sleep (1.0 + (attempt * 0.5 ))
380+ await asyncio .sleep (
381+ 2.0 + (attempt * 1.5 )
382+ ) # Start at 2s, increase by 1.5s
331383
332384 # Create a new transaction instance to avoid signature conflicts
333385 # Note: Cannot use deepcopy on Transaction objects as they
@@ -367,7 +419,9 @@ async def send_transaction(
367419 # For blockhash errors, wait longer and force RPC switch
368420 if attempt < max_retries - 1 :
369421 self ._current_rpc_index += 1 # Force RPC failover
370- await asyncio .sleep (2.5 + (attempt * 1.0 ))
422+ await asyncio .sleep (
423+ 3.0 + (attempt * 2.0 )
424+ ) # Longer waits for blockhash issues
371425 else :
372426 logger .warning (
373427 f"Transaction attempt { attempt + 1 } /{ max_retries } failed: { e } "
@@ -406,9 +460,11 @@ async def simulate_transaction(
406460 # Get fresh blockhash for each attempt using robust method
407461 fresh_blockhash = await self ._get_fresh_blockhash (max_attempts = 2 )
408462
409- # Wait a moment for blockhash propagation on retries
463+ # Wait longer for blockhash propagation on retries
410464 if attempt > 0 :
411- await asyncio .sleep (0.5 + (attempt * 0.3 ))
465+ await asyncio .sleep (
466+ 1.5 + (attempt * 1.0 )
467+ ) # Start at 1.5s, increase by 1s
412468
413469 # Create a copy of the transaction to avoid conflicts
414470 tx_copy = Transaction .from_bytes (bytes (transaction ))
0 commit comments