Skip to content

Commit 1022257

Browse files
Copilot0xrinegade
andcommitted
Fix Solana RPC blockhash parsing with robust error handling and updated endpoints
Co-authored-by: 0xrinegade <[email protected]>
1 parent 747acf5 commit 1022257

File tree

2 files changed

+82
-26
lines changed

2 files changed

+82
-26
lines changed

python/solana_ai_registries/client.py

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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))

python/solana_ai_registries/constants.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@
183183
DEFAULT_DEVNET_RPC: Final[str] = "https://api.devnet.solana.com"
184184
DEFAULT_TESTNET_RPC: Final[str] = "https://api.testnet.solana.com"
185185

186-
# Alternative RPC endpoints for failover
186+
# Alternative RPC endpoints for failover (using most reliable endpoints)
187187
FALLBACK_DEVNET_RPCS: Final[List[str]] = [
188-
"https://api.devnet.solana.com",
189-
"https://devnet.helius-rpc.com",
190-
"https://solana-devnet.g.alchemy.com/v2/demo",
191-
"https://rpc.ankr.com/solana_devnet",
188+
"https://api.devnet.solana.com", # Official Solana Labs endpoint
189+
"https://devnet.helius-rpc.com", # Helius (reliable)
190+
"https://solana-devnet.g.alchemy.com/v2/demo", # Alchemy demo
191+
"https://devnet.sonic.game", # Sonic (alternative)
192192
]
193193

194194
# Transaction Configuration

0 commit comments

Comments
 (0)