#!/usr/bin/env python3
"""
Simple AITBC Blockchain Explorer - Demonstrating the issues described in the analysis
"""
import asyncio
import re
from datetime import datetime
from typing import Dict, Any, Optional
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
import uvicorn
from aitbc.http_client import AsyncAITBCHTTPClient
from aitbc.aitbc_logging import get_logger
from aitbc.exceptions import NetworkError
app = FastAPI(title="Simple AITBC Explorer", version="0.1.0")
# Initialize logger
logger = get_logger(__name__)
# Configuration
BLOCKCHAIN_RPC_URL = "http://localhost:8025"
# Validation patterns for user inputs to prevent SSRF
TX_HASH_PATTERN = re.compile(r'^[a-fA-F0-9]{64}$') # 64-character hex string for transaction hash
def validate_tx_hash(tx_hash: str) -> bool:
"""Validate transaction hash to prevent SSRF"""
if not tx_hash:
return False
# Check for path traversal or URL manipulation
if any(char in tx_hash for char in ['/', '\\', '..', '\n', '\r', '\t', '?', '&']):
return False
# Validate against hash pattern
return bool(TX_HASH_PATTERN.match(tx_hash))
# HTML Template with the problematic frontend
HTML_TEMPLATE = """
Simple AITBC Explorer
AITBC Blockchain Explorer
"""
# Problem 1: Only /api/chain/head and /api/blocks/{height} defined, missing /api/transactions/{hash}
@app.get("/api/chain/head")
async def get_chain_head():
"""Get current chain head"""
try:
client = AsyncAITBCHTTPClient(base_url=BLOCKCHAIN_RPC_URL, timeout=10)
response = await client.async_get("/rpc/head")
if response:
return response
except NetworkError as e:
logger.error(f"Error getting chain head: {e}")
return {"height": 0, "hash": "", "timestamp": None}
@app.get("/api/blocks/{height}")
async def get_block(height: int):
"""Get block by height"""
# Validate height is non-negative and reasonable
if height < 0 or height > 10000000:
return {"height": height, "hash": "", "timestamp": None, "transactions": []}
try:
client = AsyncAITBCHTTPClient(base_url=BLOCKCHAIN_RPC_URL, timeout=10)
response = await client.async_get(f"/rpc/blocks/{height}")
if response:
return response
except NetworkError as e:
logger.error(f"Error getting block: {e}")
return {"height": height, "hash": "", "timestamp": None, "transactions": []}
@app.get("/api/transactions/{tx_hash}")
async def get_transaction(tx_hash: str):
"""Get transaction by hash - Problem 1: This endpoint was missing"""
if not validate_tx_hash(tx_hash):
return {"hash": tx_hash, "from": "unknown", "to": "unknown", "amount": 0, "timestamp": None}
try:
client = AsyncAITBCHTTPClient(base_url=BLOCKCHAIN_RPC_URL, timeout=10)
response = await client.async_get(f"/rpc/tx/{tx_hash}")
if response:
# Problem 2: Map RPC schema to UI schema
return {
"hash": response.get("tx_hash", tx_hash), # tx_hash -> hash
"from": response.get("sender", "unknown"), # sender -> from
"to": response.get("recipient", "unknown"), # recipient -> to
"amount": response.get("payload", {}).get("value", "0"), # payload.value -> amount
"fee": response.get("payload", {}).get("fee", "0"), # payload.fee -> fee
"timestamp": response.get("created_at"), # created_at -> timestamp
"block_height": response.get("block_height", "pending")
}
except NetworkError as e:
logger.error(f"Error getting transaction {tx_hash}: {e}")
raise HTTPException(status_code=500, detail=f"Failed to fetch transaction: {str(e)}")
# Missing: @app.get("/api/transactions/{tx_hash}") - THIS IS THE PROBLEM
@app.get("/", response_class=HTMLResponse)
async def root():
"""Serve the explorer UI"""
return HTML_TEMPLATE
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8017)