Spaces:
Runtime error
Runtime error
import gradio as gr | |
import bittensor as bt | |
import requests | |
import pandas as pd | |
from apscheduler.schedulers.background import BackgroundScheduler | |
# Enhanced Custom CSS | |
custom_css = """ | |
.gradio-container { | |
max-width: 1200px !important; | |
margin: auto; | |
background-color: #1a1a1a; | |
} | |
.title { | |
text-align: center; | |
margin-bottom: 2rem; | |
padding: 2rem 0; | |
background: linear-gradient(90deg, #FF4B1F 0%, #FF9068 100%); | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.title h1 { | |
color: white; | |
font-size: 2.5rem; | |
margin: 0; | |
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); | |
} | |
.title p { | |
color: rgba(255, 255, 255, 0.9); | |
font-size: 1.1rem; | |
margin: 0.5rem 0 0 0; | |
} | |
/* Style the tabs */ | |
.tabs { | |
margin-top: 1rem; | |
} | |
/* Style the DataFrame */ | |
.dataframe { | |
border-radius: 8px; | |
overflow: hidden; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
/* Style the refresh button */ | |
.refresh-btn { | |
background: linear-gradient(90deg, #FF4B1F 0%, #FF9068 100%); | |
border: none; | |
padding: 10px 20px; | |
border-radius: 5px; | |
color: white; | |
font-weight: bold; | |
cursor: pointer; | |
transition: transform 0.2s; | |
} | |
.refresh-btn:hover { | |
transform: translateY(-2px); | |
} | |
/* Status message styling */ | |
.status-msg { | |
color: #888; | |
font-style: italic; | |
margin-top: 1rem; | |
} | |
/* Custom styling for API status indicators */ | |
.api-status { | |
font-size: 1.2em; | |
} | |
.api-up { | |
color: #00ff00; | |
} | |
.api-down { | |
color: #ff0000; | |
} | |
""" | |
# Add error handling for bittensor initialization | |
try: | |
subtensor = bt.subtensor() | |
metagraph = bt.metagraph(netuid=36) | |
except Exception as e: | |
print(f"Failed to initialize bittensor: {e}") | |
subtensor = None | |
metagraph = None | |
def get_validator_data() -> pd.DataFrame: | |
if subtensor is None or metagraph is None: | |
# Return empty DataFrame with correct columns if initialization failed | |
return pd.DataFrame(columns=['Name', 'UID', 'Axon', 'API', 'Step', 'Recent Bits', 'Updated', 'VTrust']) | |
try: | |
validator_ids = list(set([i for i in range(len(metagraph.validator_permit)) | |
if metagraph.validator_permit[i] and | |
metagraph.active[i] and | |
str(metagraph.axons[i].ip) != "0.0.0.0"])) | |
except Exception as e: | |
print(f"Error getting validator IDs: {e}") | |
validator_ids = [] | |
current_block = subtensor.block | |
results = [] | |
for uid in validator_ids: | |
validator_info = { | |
'Name': 'unavailable', | |
'UID': uid, | |
'Axon': 'unavailable', | |
'Step': 0, | |
'Recent Bits': 0, | |
'Updated': 0, | |
'VTrust': 0, | |
'API': 'β' | |
} | |
try: | |
# Get validator name | |
try: | |
identity = subtensor.substrate.query('SubtensorModule', 'Identities', [metagraph.coldkeys[uid]]) | |
validator_info['Name'] = identity.value["name"] if identity != None else 'unnamed' | |
except Exception as e: | |
print(f"Error getting Name for UID {uid}: {str(e)}") | |
validator_info['Axon'] = f"{metagraph.axons[uid].ip}:{metagraph.axons[uid].port}" | |
# Get Step and Range from endpoints | |
try: | |
axon_endpoint = f"http://{validator_info['Axon']}" | |
step_response = requests.get(f"{axon_endpoint}/step", timeout=5) | |
step_response.raise_for_status() | |
validator_info['Step'] = step_response.json() | |
bits_response = requests.get( | |
f"{axon_endpoint}/bits", | |
headers={"range": "bytes=-1"}, | |
timeout=5 | |
) | |
bits_response.raise_for_status() | |
binary_string = ''.join(format(byte, '08b') for byte in bits_response.content) | |
validator_info['Recent Bits'] = binary_string[-8:] | |
validator_info['API'] = '<span class="api-status api-up">β </span>' if bits_response.ok else '<span class="api-status api-down">β</span>' | |
except requests.Timeout: | |
print(f"Timeout while connecting to {axon_endpoint}") | |
except Exception as e: | |
print(f"Error connecting to {axon_endpoint}: {e}") | |
try: | |
last_update = int(metagraph.last_update[uid]) | |
validator_info['Updated'] = current_block - last_update | |
except Exception as e: | |
print(f"Error getting Updated for UID {uid}: {str(e)}") | |
try: | |
validator_info['VTrust'] = float(metagraph.validator_trust[uid]) | |
except Exception as e: | |
print(f"Error getting VTrust for UID {uid}: {str(e)}") | |
except Exception as e: | |
print(f"Error getting Axon for UID {uid}: {str(e)}") | |
results.append(validator_info) | |
df = pd.DataFrame(results) | |
df['VTrust'] = df['VTrust'].round(4) | |
return df.sort_values('Step', ascending=False)[['Name', 'UID', 'Axon', 'API', 'Step', 'Recent Bits', 'Updated', 'VTrust']] | |
# Create the Gradio interface | |
app = gr.Blocks( | |
title="SN36 Validator Leaderboard", | |
css=""" | |
#component-0 { height: 100vh !important; } | |
.gradio-container { height: 100vh !important; } | |
.contain { height: 100vh !important; } | |
.main { height: 100% !important; } | |
.tabs { height: calc(100% - 100px) !important; } | |
.tab-panel { height: 100% !important; } | |
#leaderboard-table { height: calc(100% - 80px) !important; } | |
""" | |
) | |
# Update the HTML template | |
header_html = """ | |
<div style="text-align: center; max-width: 100%; padding: 1rem; background-color: #FF5733; border-radius: 1rem;"> | |
<h1 style="color: white;">SN36 Validator Leaderboard</h1> | |
<p style="color: white;">Real-time tracking of validator performance and bits</p> | |
</div> | |
""" | |
with app: | |
gr.HTML(header_html) | |
with gr.Tabs(elem_id="main-tabs"): | |
with gr.Tab("π Leaderboard", elem_id="leaderboard-tab"): | |
leaderboard = gr.DataFrame( | |
headers=["Name", "UID", "Axon", "API", "Step", "Recent Bits", "Updated", "VTrust"], | |
datatype=["str", "number", "str", "html", "number", "str", "number", "number"], | |
elem_id="leaderboard-table", | |
render=True | |
) | |
with gr.Row(equal_height=True): | |
refresh_button = gr.Button("π Refresh Data", variant="primary", elem_classes=["refresh-btn"]) | |
auto_refresh = gr.Checkbox( | |
label="Auto-refresh (5 min)", | |
value=True, | |
interactive=True | |
) | |
status_message = gr.Markdown("Last updated: Never", elem_classes=["status-msg"]) | |
with gr.Tab("βΉοΈ About"): | |
gr.Markdown( | |
""" | |
## About this Leaderboard | |
This dashboard shows real-time information about validators on the network: | |
- **Name**: Validator's registered name on the network | |
- **UID**: Unique identifier of the validator | |
- **Axon**: Validator's Axon address (IP:port) | |
- **API**: API status (β online, β offline) | |
- **Step**: Current step count (0 if unavailable) | |
- **Range**: Validator's bit range (0 if unavailable) | |
- **Updated**: Blocks since last update (0 if unavailable) | |
- **VTrust**: Validator's trust score (0 if unavailable) | |
Data is automatically refreshed every 5 minutes, or you can manually refresh using the button. | |
""" | |
) | |
def update_leaderboard(): | |
df = get_validator_data() | |
timestamp = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S UTC") | |
return df, f"Last updated: {timestamp}" | |
refresh_button.click( | |
fn=update_leaderboard, | |
outputs=[leaderboard, status_message], | |
queue=False | |
) | |
# Auto-refresh logic | |
def setup_auto_refresh(): | |
app.scheduler = BackgroundScheduler() | |
app.scheduler.add_job( | |
lambda: app.queue(update_leaderboard), | |
'interval', | |
minutes=5 | |
) | |
app.scheduler.start() | |
# Initial data load | |
app.load( | |
fn=update_leaderboard, | |
outputs=[leaderboard, status_message] | |
) | |
setup_auto_refresh() | |
# Launch the interface | |
app.launch() |