Automated Action 85b1f2a581 Enhance Solana RPC client with better error handling and debugging
- Add detailed logging throughout the Solana client and scanner
- Improve error handling in RPC client methods
- Add debug endpoints to validate Solana connection
- Add message field to scan status responses
- Enhance health endpoint with RPC connectivity status
- Handle invalid block ranges and API rate limits
2025-05-28 23:08:35 +00:00

270 lines
9.4 KiB
Python

from typing import Dict, List
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.models.models import ArbitrageEvent
from app.schemas.schemas import (ArbitrageEventResponse, ScanRequest,
ScanStatusResponse)
from app.services.scanner import BlockScanner
from app.services.solana_client import SolanaClient
router = APIRouter()
@router.post("/scan", response_model=ScanStatusResponse)
async def scan_for_arbitrage(
background_tasks: BackgroundTasks,
scan_request: ScanRequest,
db: Session = Depends(get_db),
):
"""
Scan the blockchain for arbitrage transactions
"""
scanner = BlockScanner(db)
# Check if scan is already in progress
if scanner.scan_in_progress:
return ScanStatusResponse(
last_scanned_block=scanner.get_last_scanned_block() or 0,
last_scan_time=scanner.get_scan_status()["last_scan_time"],
arbitrage_events_count=scanner.get_scan_status()["arbitrage_events_count"],
scan_in_progress=True,
message="Scan already in progress, please wait for it to complete"
)
try:
# Verify Solana connection before starting scan
client = SolanaClient()
latest_block = client.get_latest_block()
if not latest_block:
return ScanStatusResponse(
last_scanned_block=scanner.get_last_scanned_block() or 0,
last_scan_time=scanner.get_scan_status()["last_scan_time"],
arbitrage_events_count=scanner.get_scan_status()["arbitrage_events_count"],
scan_in_progress=False,
message="Failed to connect to Solana RPC. Please check your connection and try again."
)
except Exception as e:
return ScanStatusResponse(
last_scanned_block=scanner.get_last_scanned_block() or 0,
last_scan_time=scanner.get_scan_status()["last_scan_time"],
arbitrage_events_count=scanner.get_scan_status()["arbitrage_events_count"],
scan_in_progress=False,
message=f"Error connecting to Solana RPC: {str(e)}"
)
# Add scanning task to background tasks
background_tasks.add_task(
scanner.scan_blocks,
num_blocks=scan_request.num_blocks,
start_slot=scan_request.start_slot,
)
# Return current scan status
status = scanner.get_scan_status()
status["message"] = "Scan started successfully"
return ScanStatusResponse(**status)
@router.get("/status", response_model=ScanStatusResponse)
async def get_scan_status(
db: Session = Depends(get_db),
):
"""
Get the current status of the arbitrage scanner
"""
scanner = BlockScanner(db)
status = scanner.get_scan_status()
# Add helpful message based on status
if status["scan_in_progress"]:
status["message"] = "Scan is currently in progress"
elif status["last_scanned_block"] == 0:
status["message"] = "No blocks have been scanned yet. Use the /scan endpoint to start scanning."
else:
status["message"] = f"Last scan completed at {status['last_scan_time']}. Found {status['arbitrage_events_count']} arbitrage events."
return ScanStatusResponse(**status)
@router.get("/events", response_model=List[ArbitrageEventResponse])
async def get_arbitrage_events(
skip: int = 0,
limit: int = 100,
min_confidence: float = 0.0,
token_address: str = None,
db: Session = Depends(get_db),
):
"""
Get detected arbitrage events
"""
query = db.query(ArbitrageEvent)
# Apply filters
if min_confidence > 0:
query = query.filter(ArbitrageEvent.confidence_score >= min_confidence)
if token_address:
query = query.filter(ArbitrageEvent.profit_token_address == token_address)
# Get paginated results
events = query.order_by(ArbitrageEvent.detected_at.desc()).offset(skip).limit(limit).all()
# Convert to response format
result = []
for event in events:
# Get transaction signature and block slot
transaction = event.transaction
block_slot = transaction.block_id if transaction else 0
result.append(
ArbitrageEventResponse(
id=event.id,
transaction_signature=transaction.signature if transaction else "",
profit_token_address=event.profit_token_address,
profit_amount=event.profit_amount,
profit_usd=event.profit_usd,
path=event.path,
confidence_score=event.confidence_score,
detected_at=event.detected_at,
block_slot=block_slot,
)
)
return result
@router.get("/events/{event_id}", response_model=ArbitrageEventResponse)
async def get_arbitrage_event(
event_id: int,
db: Session = Depends(get_db),
):
"""
Get a specific arbitrage event by ID
"""
event = db.query(ArbitrageEvent).filter(ArbitrageEvent.id == event_id).first()
if not event:
raise HTTPException(status_code=404, detail="Arbitrage event not found")
# Get transaction signature and block slot
transaction = event.transaction
block_slot = transaction.block_id if transaction else 0
return ArbitrageEventResponse(
id=event.id,
transaction_signature=transaction.signature if transaction else "",
profit_token_address=event.profit_token_address,
profit_amount=event.profit_amount,
profit_usd=event.profit_usd,
path=event.path,
confidence_score=event.confidence_score,
detected_at=event.detected_at,
block_slot=block_slot,
)
@router.get("/debug/solana-connection", response_model=Dict)
async def debug_solana_connection():
"""
Debug endpoint to test Solana RPC connection
"""
client = SolanaClient()
results = {}
try:
# Test getting latest blockhash
latest_block = client.get_latest_block()
results["latest_blockhash"] = latest_block
# Try to get a slot
slot_response = client.client.get_slot()
if "result" in slot_response:
results["current_slot"] = slot_response["result"]
# Try to get a block
try:
block_data = client.get_block(slot_response["result"])
results["block_data"] = {"has_data": block_data is not None}
if block_data:
results["block_data"]["parent_slot"] = block_data.get("parentSlot")
results["block_data"]["transactions_count"] = len(block_data.get("transactions", []))
except Exception as e:
results["block_error"] = str(e)
except Exception as e:
results["error"] = str(e)
# Add RPC URL info
results["rpc_url"] = client.rpc_url
return results
@router.get("/validate-connection", response_model=Dict)
async def validate_solana_connection():
"""
Validate the Solana RPC connection and return detailed status
"""
client = SolanaClient()
result = {
"status": "success",
"rpc_url": client.rpc_url,
"connection_valid": False,
"message": "",
}
try:
# Test 1: Get latest blockhash
latest_blockhash = client.get_latest_block()
result["blockhash_test"] = {
"success": latest_blockhash is not None,
"data": latest_blockhash if latest_blockhash else "Failed to retrieve"
}
# Test 2: Get current slot
try:
slot_response = client.client.get_slot()
current_slot = slot_response.get("result") if "result" in slot_response else None
result["slot_test"] = {
"success": current_slot is not None,
"data": current_slot if current_slot else "Failed to retrieve"
}
# If we have a slot, try to get a block
if current_slot:
try:
block = client.get_block(current_slot)
result["block_test"] = {
"success": block is not None,
"data": {
"has_transactions": block is not None and "transactions" in block,
"transaction_count": len(block.get("transactions", [])) if block else 0
}
}
except Exception as e:
result["block_test"] = {
"success": False,
"error": str(e)
}
except Exception as e:
result["slot_test"] = {
"success": False,
"error": str(e)
}
# Determine overall status
if result.get("blockhash_test", {}).get("success", False) and result.get("slot_test", {}).get("success", False):
result["connection_valid"] = True
result["message"] = "Solana RPC connection is valid and functioning properly."
else:
result["status"] = "error"
result["message"] = "Solana RPC connection is not fully functional."
except Exception as e:
result["status"] = "error"
result["message"] = f"Error validating Solana RPC connection: {str(e)}"
return result