Spaces:
Runtime error
Runtime error
alex n
commited on
Commit
·
f909ba9
1
Parent(s):
cbf3f1e
fixed website added better requirements file
Browse files- app.py +250 -244
- requirements.txt +117 -3
app.py
CHANGED
@@ -1,270 +1,276 @@
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
3 |
-
|
4 |
-
import
|
5 |
-
from
|
|
|
6 |
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
try:
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
except Exception as e:
|
18 |
-
print(f"Error
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
-
|
22 |
results = []
|
23 |
|
24 |
-
for
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
'VTrust': 0,
|
33 |
-
'API': '❌'
|
34 |
-
}
|
35 |
-
|
36 |
-
try:
|
37 |
-
# Get validator name
|
38 |
-
try:
|
39 |
-
identity = subtensor.substrate.query('SubtensorModule', 'Identities', [metagraph.coldkeys[uid]])
|
40 |
-
validator_info['Name'] = identity.value["name"] if identity != None else 'unnamed'
|
41 |
-
except Exception as e:
|
42 |
-
print(f"Error getting Name for UID {uid}: {str(e)}")
|
43 |
-
|
44 |
-
validator_info['Axon'] = f"{metagraph.axons[uid].ip}:{metagraph.axons[uid].port}"
|
45 |
-
|
46 |
-
# Get Step and Range from endpoints
|
47 |
-
try:
|
48 |
-
axon_endpoint = f"http://{validator_info['Axon']}"
|
49 |
-
step_response = requests.get(f"{axon_endpoint}/step", timeout=(2, 3))
|
50 |
-
step_response.raise_for_status()
|
51 |
-
validator_info['Step'] = step_response.json()
|
52 |
-
|
53 |
-
bits_response = requests.get(
|
54 |
-
f"{axon_endpoint}/bits",
|
55 |
-
headers={"range": "bytes=-1"},
|
56 |
-
timeout=(2, 3)
|
57 |
-
)
|
58 |
-
bits_response.raise_for_status()
|
59 |
-
binary_string = ''.join(format(byte, '08b') for byte in bits_response.content)
|
60 |
-
validator_info['Recent Bits'] = binary_string[-8:]
|
61 |
-
validator_info['API'] = '<span class="api-status api-up">✅</span>' if bits_response.ok else '<span class="api-status api-down">❌</span>'
|
62 |
-
|
63 |
-
except requests.Timeout:
|
64 |
-
validator_info['API'] = '<span class="api-status api-down">❌</span>'
|
65 |
-
except Exception as e:
|
66 |
-
if not isinstance(e, requests.Timeout):
|
67 |
-
print(f"Error connecting to {axon_endpoint}: {str(e)}")
|
68 |
-
validator_info['API'] = '<span class="api-status api-down">❌</span>'
|
69 |
-
|
70 |
-
try:
|
71 |
-
last_update = int(metagraph.last_update[uid])
|
72 |
-
validator_info['Updated'] = current_block - last_update
|
73 |
-
except Exception as e:
|
74 |
-
print(f"Error getting Updated for UID {uid}: {str(e)}")
|
75 |
|
76 |
-
|
77 |
-
|
78 |
-
except Exception as e:
|
79 |
-
print(f"Error getting VTrust for UID {uid}: {str(e)}")
|
80 |
-
|
81 |
-
except Exception as e:
|
82 |
-
print(f"Error getting Axon for UID {uid}: {str(e)}")
|
83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
results.append(validator_info)
|
85 |
|
86 |
df = pd.DataFrame(results)
|
87 |
df['VTrust'] = df['VTrust'].round(4)
|
88 |
-
return df.sort_values('
|
89 |
|
90 |
-
#
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
custom_css = """
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
padding: 0 !important;
|
114 |
-
margin: 0 !important;
|
115 |
-
background-image: url('""" + background_url + """') !important;
|
116 |
-
background-size: cover !important;
|
117 |
-
background-position: center !important;
|
118 |
-
background-repeat: no-repeat !important;
|
119 |
-
min-height: 100vh !important;
|
120 |
-
}
|
121 |
-
|
122 |
-
.header-box {
|
123 |
-
text-align: center;
|
124 |
-
max-width: 100%;
|
125 |
-
margin: 20px auto;
|
126 |
-
padding: 1rem;
|
127 |
-
background-color: rgba(17, 24, 39, 0.95); /* Darker, more opaque background */
|
128 |
-
border-radius: 1rem;
|
129 |
-
}
|
130 |
-
|
131 |
-
.header-box h1 {
|
132 |
-
color: white;
|
133 |
-
margin: 0;
|
134 |
-
font-size: 2rem;
|
135 |
-
}
|
136 |
-
|
137 |
-
.header-box p {
|
138 |
-
color: white; /* Changed to white instead of gray */
|
139 |
-
margin-top: 0.5rem;
|
140 |
-
}
|
141 |
-
"""
|
142 |
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
|
|
|
|
|
|
147 |
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
print(f"Error creating leaderboard: {e}")
|
156 |
-
return pd.DataFrame()
|
157 |
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
error_msg = f"Error loading data: {str(e)}"
|
166 |
-
success = False
|
167 |
-
return data, error_msg, not success
|
168 |
-
|
169 |
-
# Add this before creating the app
|
170 |
-
header_html = """
|
171 |
-
<div class="header-box">
|
172 |
-
<h1>SN36 Validator Leaderboard</h1>
|
173 |
-
<p>Real-time validator status monitoring</p>
|
174 |
-
</div>
|
175 |
-
"""
|
176 |
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
theme=gr.themes.Soft().set(
|
183 |
-
body_background_fill="rgba(17, 24, 39, 0.95)",
|
184 |
-
background_fill_secondary="rgba(17, 24, 39, 0.95)",
|
185 |
-
)
|
186 |
-
)
|
187 |
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
with gr.Tab("📊 Leaderboard", elem_id="leaderboard-tab"):
|
193 |
-
# Initialize with preloaded data
|
194 |
-
leaderboard = gr.DataFrame(
|
195 |
-
value=initial_df,
|
196 |
-
headers=["Name", "UID", "Axon", "API", "Step", "Recent Bits", "Updated", "VTrust"],
|
197 |
-
datatype=["str", "number", "str", "html", "number", "str", "number", "number"],
|
198 |
-
elem_id="leaderboard-table",
|
199 |
-
render=True
|
200 |
-
)
|
201 |
-
|
202 |
-
# Initialize with preloaded timestamp
|
203 |
-
status_message = gr.Markdown(
|
204 |
-
value=f"Last updated: {initial_timestamp}",
|
205 |
-
elem_classes=["status-msg"]
|
206 |
-
)
|
207 |
-
|
208 |
-
with gr.Row(equal_height=True):
|
209 |
-
refresh_button = gr.Button("🔄 Refresh Data", variant="primary", elem_classes=["refresh-btn"])
|
210 |
-
auto_refresh = gr.Checkbox(
|
211 |
-
label="Auto-refresh (5 min)",
|
212 |
-
value=True,
|
213 |
-
interactive=True
|
214 |
-
)
|
215 |
-
|
216 |
-
with gr.Tab("ℹ️ About"):
|
217 |
-
gr.Markdown(
|
218 |
-
"""
|
219 |
-
<div style="color: white;">
|
220 |
-
## About this Leaderboard
|
221 |
-
|
222 |
-
This dashboard shows real-time information about validators on the network:
|
223 |
-
|
224 |
-
- **Name**: Validator's registered name on the network
|
225 |
-
- **UID**: Unique identifier of the validator
|
226 |
-
- **Axon**: Validator's Axon address (IP:port)
|
227 |
-
- **API**: API status (✅ online, ❌ offline)
|
228 |
-
- **Step**: Current step count (0 if unavailable)
|
229 |
-
- **Range**: Validator's bit range (0 if unavailable)
|
230 |
-
- **Updated**: Blocks since last update (0 if unavailable)
|
231 |
-
- **VTrust**: Validator's trust score (0 if unavailable)
|
232 |
-
|
233 |
-
Data is automatically refreshed every 5 minutes, or you can manually refresh using the button.
|
234 |
-
</div>
|
235 |
-
"""
|
236 |
-
)
|
237 |
-
|
238 |
-
def update_leaderboard():
|
239 |
-
df = get_validator_data()
|
240 |
-
timestamp = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S UTC")
|
241 |
-
return df, f"Last updated: {timestamp}"
|
242 |
-
|
243 |
-
refresh_button.click(
|
244 |
-
fn=update_leaderboard,
|
245 |
-
outputs=[leaderboard, status_message],
|
246 |
-
queue=False
|
247 |
-
)
|
248 |
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
)
|
257 |
-
|
|
|
|
|
|
|
258 |
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
)
|
264 |
|
265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
|
267 |
-
|
268 |
-
|
269 |
-
share=False
|
270 |
-
)
|
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
3 |
+
from concurrent.futures import ThreadPoolExecutor
|
4 |
+
from datetime import datetime, timedelta
|
5 |
+
from math import ceil
|
6 |
+
from typing import TypeAlias, Optional, Tuple
|
7 |
|
8 |
+
import aiohttp
|
9 |
+
import asyncio
|
10 |
+
|
11 |
+
from fiber.chain.interface import get_substrate
|
12 |
+
from fiber.chain.metagraph import Metagraph
|
13 |
+
from fiber.chain.models import Node
|
14 |
+
from substrateinterface.storage import StorageKey
|
15 |
+
|
16 |
+
Weight: TypeAlias = float
|
17 |
+
Hotkey: TypeAlias = str
|
18 |
+
Uid: TypeAlias = int
|
19 |
+
|
20 |
+
NET_UID = 36
|
21 |
+
VALIDATOR_IDENTITIES: dict[Hotkey, str] = {}
|
22 |
+
UPDATED: dict[Hotkey, int] = {}
|
23 |
+
UIDS_BY_HOTKEY: dict[Hotkey, Uid] = {}
|
24 |
+
HOTKEYS_BY_UID: dict[Uid, Hotkey] = {}
|
25 |
+
|
26 |
+
substrate = get_substrate()
|
27 |
+
metagraph = Metagraph(substrate, netuid=NET_UID, load_old_nodes=False)
|
28 |
+
|
29 |
+
def query_subtensor(storage_keys: list[StorageKey], block: int) -> list:
|
30 |
+
global substrate
|
31 |
try:
|
32 |
+
return substrate.query_multi(
|
33 |
+
storage_keys=storage_keys,
|
34 |
+
block_hash=substrate.get_block_hash(block),
|
35 |
+
)
|
36 |
+
except Exception:
|
37 |
+
substrate = get_substrate()
|
38 |
+
raise
|
39 |
+
|
40 |
+
def is_validator(node: Node) -> bool:
|
41 |
+
if not hasattr(node, 'vtrust') or not hasattr(node, 'ip'):
|
42 |
+
return False
|
43 |
+
return node.vtrust > 0 and str(node.ip) != "0.0.0.0"
|
44 |
+
|
45 |
+
def fetch_updated(block: int):
|
46 |
+
UPDATED.clear()
|
47 |
+
for hotkey, node in metagraph.nodes.items():
|
48 |
+
UPDATED[hotkey] = ceil(block - node.last_updated)
|
49 |
+
|
50 |
+
def fetch_identities(block: int):
|
51 |
+
VALIDATOR_IDENTITIES.clear()
|
52 |
+
storage_keys: list[StorageKey] = []
|
53 |
+
for hotkey, node in metagraph.nodes.items():
|
54 |
+
if not is_validator(node): continue
|
55 |
+
storage_keys.append(substrate.create_storage_key(
|
56 |
+
"SubtensorModule",
|
57 |
+
"Identities",
|
58 |
+
[node.coldkey]
|
59 |
+
))
|
60 |
+
|
61 |
+
identities = query_subtensor(storage_keys, block)
|
62 |
+
for hotkey, node in metagraph.nodes.items():
|
63 |
+
for storage, info in identities:
|
64 |
+
if node.coldkey != storage.params[0]: continue
|
65 |
+
if info is not None:
|
66 |
+
VALIDATOR_IDENTITIES[hotkey] = info.value["name"]
|
67 |
+
break
|
68 |
+
|
69 |
+
async def fetch_validator_stats(session: aiohttp.ClientSession, ip: str) -> Tuple[Optional[int], Optional[str], bool]:
|
70 |
+
try:
|
71 |
+
# Fetch current step (in bits)
|
72 |
+
step_url = f"http://{ip}/step"
|
73 |
+
async with session.get(step_url, timeout=5) as resp:
|
74 |
+
if resp.status == 200:
|
75 |
+
step = await resp.json()
|
76 |
+
byte_pos = step // 8
|
77 |
+
byte_range = f"bytes={byte_pos}-{byte_pos}"
|
78 |
+
print(f"Got step {step}, using range: {byte_range}")
|
79 |
+
else:
|
80 |
+
print(f"Failed to get step from {ip}: {resp.status}")
|
81 |
+
return None, None, False
|
82 |
+
|
83 |
+
# Get byte at step position
|
84 |
+
bits_url = f"http://{ip}/bits"
|
85 |
+
headers = {'Range': byte_range}
|
86 |
+
async with session.get(bits_url, headers=headers, timeout=5) as resp:
|
87 |
+
if resp.status == 206:
|
88 |
+
bytes_data = await resp.read()
|
89 |
+
if bytes_data:
|
90 |
+
last_byte = format(bytes_data[0], '08b')
|
91 |
+
print(f"Got byte from {ip}: {last_byte}")
|
92 |
+
return step, last_byte, True
|
93 |
+
else:
|
94 |
+
print(f"No byte data from {ip}")
|
95 |
+
return None, None, False
|
96 |
+
else:
|
97 |
+
print(f"Failed byte request for {ip}: {resp.status}")
|
98 |
+
return None, None, False
|
99 |
+
|
100 |
except Exception as e:
|
101 |
+
print(f"Error for {ip}: {str(e)}")
|
102 |
+
return None, None, False
|
103 |
+
|
104 |
+
async def fetch_all_validators(ips: list[str]) -> dict[str, Tuple[Optional[int], Optional[str], bool]]:
|
105 |
+
async with aiohttp.ClientSession() as session:
|
106 |
+
tasks = [fetch_validator_stats(session, ip) for ip in ips]
|
107 |
+
results = await asyncio.gather(*tasks)
|
108 |
+
return dict(zip(ips, results))
|
109 |
|
110 |
+
def get_validator_data() -> pd.DataFrame:
|
111 |
results = []
|
112 |
|
113 |
+
ips = [f"{node.ip}:{node.port}" for hotkey, node in metagraph.nodes.items()
|
114 |
+
if is_validator(node)]
|
115 |
+
|
116 |
+
stats = asyncio.run(fetch_all_validators(ips))
|
117 |
+
|
118 |
+
for hotkey, node in metagraph.nodes.items():
|
119 |
+
if not is_validator(node):
|
120 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
|
122 |
+
ip = f"{node.ip}:{node.port}"
|
123 |
+
step, last_byte, api_up = stats.get(ip, (None, None, False))
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
+
validator_info = {
|
126 |
+
'Name': VALIDATOR_IDENTITIES.get(hotkey, 'unnamed'),
|
127 |
+
'UID': UIDS_BY_HOTKEY.get(hotkey, -1),
|
128 |
+
'VTrust': float(node.vtrust),
|
129 |
+
'IP': ip,
|
130 |
+
'API': '✅' if api_up else '❌',
|
131 |
+
'Step': step if step is not None else 'N/A',
|
132 |
+
'Last Byte': last_byte if last_byte is not None else 'N/A',
|
133 |
+
'Updated': UPDATED.get(hotkey, 0)
|
134 |
+
}
|
135 |
results.append(validator_info)
|
136 |
|
137 |
df = pd.DataFrame(results)
|
138 |
df['VTrust'] = df['VTrust'].round(4)
|
139 |
+
return df.sort_values('VTrust', ascending=False)
|
140 |
|
141 |
+
# Sync logic
|
142 |
+
last_sync: datetime = datetime.fromtimestamp(0)
|
143 |
+
last_identity_sync: datetime = datetime.fromtimestamp(0)
|
144 |
+
|
145 |
+
def sync_metagraph(timeout: int = 10):
|
146 |
+
global substrate, last_sync
|
147 |
+
now = datetime.now()
|
148 |
+
if now - last_sync < timedelta(minutes=5):
|
149 |
+
return
|
150 |
+
last_sync = now
|
151 |
+
|
152 |
+
def sync_task():
|
153 |
+
print("Syncing metagraph...")
|
154 |
+
block = substrate.get_block_number(None)
|
155 |
+
metagraph.sync_nodes()
|
156 |
+
|
157 |
+
for uid, node in enumerate(metagraph.nodes.values()):
|
158 |
+
UIDS_BY_HOTKEY[node.hotkey] = uid
|
159 |
+
HOTKEYS_BY_UID[uid] = node.hotkey
|
160 |
+
|
161 |
+
fetch_updated(block)
|
162 |
+
|
163 |
+
global last_identity_sync
|
164 |
+
if now - last_identity_sync > timedelta(hours=1):
|
165 |
+
print("Syncing identities...")
|
166 |
+
last_identity_sync = now
|
167 |
+
fetch_identities(block)
|
168 |
+
|
169 |
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
170 |
+
future = executor.submit(sync_task)
|
171 |
+
try:
|
172 |
+
future.result(timeout=timeout)
|
173 |
+
except Exception as e:
|
174 |
+
print(f"Error syncing metagraph: {e}")
|
175 |
+
substrate = get_substrate()
|
176 |
+
|
177 |
+
# Create Gradio interface
|
178 |
custom_css = """
|
179 |
+
:root {
|
180 |
+
--primary: #2A4365; /* Base blue */
|
181 |
+
--primary-light: #3B5C8F; /* Lighter blue for hover states */
|
182 |
+
--primary-dark: #1A2F4C; /* Darker blue for backgrounds */
|
183 |
+
--primary-fade: #4A6285; /* Faded blue for secondary elements */
|
184 |
+
--text-primary: #EDF2F7; /* Light gray for main text */
|
185 |
+
--text-secondary: #CBD5E0; /* Darker gray for secondary text */
|
186 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
|
188 |
+
.gradio-container {
|
189 |
+
background-image: url('https://huggingface.co/spaces/wombo/pyramid-scheme-validator-states/resolve/main/assets/background2.png') !important;
|
190 |
+
background-size: cover !important;
|
191 |
+
background-position: center !important;
|
192 |
+
background-attachment: fixed !important;
|
193 |
+
min-height: 100vh !important;
|
194 |
+
}
|
195 |
|
196 |
+
/* Style the table */
|
197 |
+
.table-wrap {
|
198 |
+
background-color: var(--primary) !important;
|
199 |
+
border-radius: 8px !important;
|
200 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
|
201 |
+
opacity: 0.95 !important; /* Slight transparency to show background */
|
202 |
+
}
|
|
|
|
|
203 |
|
204 |
+
/* Style the table headers */
|
205 |
+
thead tr th {
|
206 |
+
background-color: var(--primary-fade) !important;
|
207 |
+
color: var(--text-primary) !important;
|
208 |
+
font-weight: 600 !important;
|
209 |
+
border-bottom: 2px solid var(--primary-light) !important;
|
210 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
|
212 |
+
/* Style table cells */
|
213 |
+
tbody tr td {
|
214 |
+
color: var(--text-primary) !important;
|
215 |
+
border-bottom: 1px solid var(--primary-light) !important;
|
216 |
+
}
|
|
|
|
|
|
|
|
|
|
|
217 |
|
218 |
+
/* Alternating row colors */
|
219 |
+
tbody tr:nth-child(even) {
|
220 |
+
background-color: var(--primary-dark) !important;
|
221 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
222 |
|
223 |
+
/* Row hover effect */
|
224 |
+
tbody tr:hover {
|
225 |
+
background-color: var(--primary-light) !important;
|
226 |
+
}
|
227 |
+
|
228 |
+
/* Style the refresh button */
|
229 |
+
button.primary {
|
230 |
+
background-color: var(--primary) !important;
|
231 |
+
color: var(--text-primary) !important;
|
232 |
+
border: 1px solid var(--primary-light) !important;
|
233 |
+
transition: all 0.2s ease !important;
|
234 |
+
}
|
235 |
|
236 |
+
button.primary:hover {
|
237 |
+
background-color: var(--primary-light) !important;
|
238 |
+
transform: translateY(-1px) !important;
|
239 |
+
}
|
240 |
+
|
241 |
+
/* Style the title */
|
242 |
+
h1 {
|
243 |
+
color: var(--text-primary) !important;
|
244 |
+
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
245 |
+
}
|
246 |
+
"""
|
247 |
+
|
248 |
+
with gr.Blocks(title="📊 SN36 Validator States", css=custom_css) as demo:
|
249 |
+
gr.Markdown("""
|
250 |
+
<h1 style="text-align: center; font-size: 3em; margin: 0.5em 0;">
|
251 |
+
📊 SN36 Validator States
|
252 |
+
</h1>
|
253 |
+
""", elem_id="title")
|
254 |
+
|
255 |
+
# Initialize table with empty DataFrame
|
256 |
+
sync_metagraph() # Initial sync
|
257 |
+
initial_df = get_validator_data()
|
258 |
+
|
259 |
+
table = gr.DataFrame(
|
260 |
+
value=initial_df,
|
261 |
+
headers=['Name', 'UID', 'VTrust', 'IP', 'API', 'Step', 'Last Byte', 'Updated'],
|
262 |
+
datatype=['str', 'number', 'number', 'str', 'str', 'str', 'str', 'number'],
|
263 |
+
interactive=False
|
264 |
)
|
265 |
|
266 |
+
refresh_btn = gr.Button("Refresh")
|
267 |
+
|
268 |
+
def update():
|
269 |
+
sync_metagraph()
|
270 |
+
return get_validator_data()
|
271 |
+
|
272 |
+
refresh_btn.click(fn=update, outputs=table)
|
273 |
+
demo.load(fn=update, outputs=table)
|
274 |
|
275 |
+
if __name__ == "__main__":
|
276 |
+
demo.launch(share=False, debug=True, allowed_paths=["assets"])
|
|
|
|
requirements.txt
CHANGED
@@ -1,5 +1,119 @@
|
|
1 |
-
|
2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
pandas>=2.2.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
requests>=2.32.3
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fiber @ git+https://github.com/rayonlabs/fiber.git@1.0.0#egg=fiber[chain]
|
2 |
+
aiofiles>=23.2.1
|
3 |
+
aiohappyeyeballs>=2.4.3
|
4 |
+
aiohttp>=3.10.11
|
5 |
+
aiosignal>=1.3.1
|
6 |
+
annotated-types>=0.7.0
|
7 |
+
ansible>=6.7.0
|
8 |
+
ansible-core>=2.13.13
|
9 |
+
ansible-vault>=2.1.0
|
10 |
+
anyio>=4.6.2.post1
|
11 |
+
async-property>=0.2.2
|
12 |
+
async-timeout>=5.0.1
|
13 |
+
attrs>=24.2.0
|
14 |
+
backoff>=2.2.1
|
15 |
+
base58>=2.1.1
|
16 |
+
bittensor>=8.2.1
|
17 |
+
bittensor-cli>=8.3.1
|
18 |
+
bittensor-wallet>=2.1.1
|
19 |
+
bt-decode>=0.2.0a0
|
20 |
+
certifi>=2024.8.30
|
21 |
+
cffi>=1.17.1
|
22 |
+
charset-normalizer>=3.4.0
|
23 |
+
click>=8.1.7
|
24 |
+
colorama>=0.4.6
|
25 |
+
cryptography>=43.0.3
|
26 |
+
cytoolz>=1.0.0
|
27 |
+
decorator>=5.1.1
|
28 |
+
ecdsa>=0.19.0
|
29 |
+
eth-hash>=0.7.0
|
30 |
+
eth-keys>=0.6.0
|
31 |
+
eth-typing>=5.0.0
|
32 |
+
eth-utils>=2.2.2
|
33 |
+
exceptiongroup>=1.2.2
|
34 |
+
fastapi>=0.110.3
|
35 |
+
ffmpy>=0.4.0
|
36 |
+
fiber>=1.0.0
|
37 |
+
filelock>=3.16.1
|
38 |
+
frozenlist>=1.5.0
|
39 |
+
fsspec>=2024.10.0
|
40 |
+
fuzzywuzzy>=0.18.0
|
41 |
+
gitdb>=4.0.11
|
42 |
+
GitPython>=3.1.43
|
43 |
+
gradio>=5.1.0
|
44 |
+
gradio_client>=1.4.0
|
45 |
+
h11>=0.14.0
|
46 |
+
httpcore>=1.0.7
|
47 |
+
httpx>=0.27.2
|
48 |
+
huggingface-hub>=0.26.2
|
49 |
+
idna>=3.10
|
50 |
+
iniconfig>=2.0.0
|
51 |
+
Jinja2>=3.1.4
|
52 |
+
Levenshtein>=0.26.1
|
53 |
+
markdown-it-py>=3.0.0
|
54 |
+
MarkupSafe>=2.1.5
|
55 |
+
mdurl>=0.1.2
|
56 |
+
more-itertools>=10.5.0
|
57 |
+
msgpack>=1.1.0
|
58 |
+
msgpack-numpy-opentensor>=0.5.0
|
59 |
+
multidict>=6.1.0
|
60 |
+
munch>=2.5.0
|
61 |
+
nest-asyncio>=1.6.0
|
62 |
+
netaddr>=1.3.0
|
63 |
+
numpy>=2.0.2
|
64 |
+
orjson>=3.10.11
|
65 |
+
packaging>=24.2
|
66 |
pandas>=2.2.3
|
67 |
+
password-strength>=0.0.3.post2
|
68 |
+
pillow>=10.4.0
|
69 |
+
pluggy>=1.5.0
|
70 |
+
propcache>=0.2.0
|
71 |
+
py>=1.11.0
|
72 |
+
py-bip39-bindings>=0.1.11
|
73 |
+
py-ed25519-zebra-bindings>=1.1.0
|
74 |
+
py-sr25519-bindings>=0.2.1
|
75 |
+
pycparser>=2.22
|
76 |
+
pycryptodome>=3.21.0
|
77 |
+
pydantic>=2.9.2
|
78 |
+
pydantic_core>=2.23.4
|
79 |
+
pydub>=0.25.1
|
80 |
+
Pygments>=2.18.0
|
81 |
+
PyNaCl>=1.5.0
|
82 |
+
pytest>=8.3.3
|
83 |
+
python-dateutil>=2.9.0.post0
|
84 |
+
python-dotenv>=1.0.1
|
85 |
+
python-Levenshtein>=0.26.1
|
86 |
+
python-multipart>=0.0.17
|
87 |
+
python-statemachine>=2.4.0
|
88 |
+
pytz>=2024.2
|
89 |
+
PyYAML>=6.0.2
|
90 |
+
RapidFuzz>=3.10.1
|
91 |
requests>=2.32.3
|
92 |
+
resolvelib>=0.8.1
|
93 |
+
retry>=0.9.2
|
94 |
+
rich>=13.9.4
|
95 |
+
ruff>=0.7.4
|
96 |
+
scalecodec>=1.2.11
|
97 |
+
semantic-version>=2.10.0
|
98 |
+
shellingham>=1.5.4
|
99 |
+
six>=1.16.0
|
100 |
+
smmap>=5.0.1
|
101 |
+
sniffio>=1.3.1
|
102 |
+
starlette>=0.37.2
|
103 |
+
substrate-interface>=1.7.10
|
104 |
+
tenacity>=9.0.0
|
105 |
+
termcolor>=2.5.0
|
106 |
+
toml>=0.10.0
|
107 |
+
tomli>=2.1.0
|
108 |
+
tomlkit>=0.12.0
|
109 |
+
toolz>=1.0.0
|
110 |
+
tqdm>=4.67.0
|
111 |
+
typer>=0.13.1
|
112 |
+
typing_extensions>=4.12.2
|
113 |
+
tzdata>=2024.2
|
114 |
+
urllib3>=2.2.3
|
115 |
+
uvicorn>=0.32.1
|
116 |
+
websocket-client>=1.8.0
|
117 |
+
websockets>=12.0
|
118 |
+
xxhash>=3.5.0
|
119 |
+
yarl>=1.18.0
|