blockmachine 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {blockmachine-0.3.0 → blockmachine-0.3.2}/PKG-INFO +1 -1
- {blockmachine-0.3.0 → blockmachine-0.3.2}/blockmachine.egg-info/PKG-INFO +1 -1
- {blockmachine-0.3.0 → blockmachine-0.3.2}/commands/miner.py +107 -19
- {blockmachine-0.3.0 → blockmachine-0.3.2}/pyproject.toml +1 -1
- {blockmachine-0.3.0 → blockmachine-0.3.2}/__init__.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/blockmachine.egg-info/SOURCES.txt +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/blockmachine.egg-info/dependency_links.txt +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/blockmachine.egg-info/entry_points.txt +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/blockmachine.egg-info/requires.txt +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/blockmachine.egg-info/top_level.txt +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/client.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/commands/__init__.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/commands/auth.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/commands/validator.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/config.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/main.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/settings.py +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/setup.cfg +0 -0
- {blockmachine-0.3.0 → blockmachine-0.3.2}/utils.py +0 -0
|
@@ -8,7 +8,7 @@ import logging
|
|
|
8
8
|
import socket
|
|
9
9
|
import ssl
|
|
10
10
|
import time
|
|
11
|
-
from typing import Optional
|
|
11
|
+
from typing import Callable, Optional
|
|
12
12
|
from urllib.parse import urlparse
|
|
13
13
|
|
|
14
14
|
import httpx
|
|
@@ -116,6 +116,17 @@ def _test_tls(hostname: str, port: int) -> tuple[bool, Optional[str], str]:
|
|
|
116
116
|
return False, None, str(e)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
def _bracket_ipv6(hostname: str) -> str:
|
|
120
|
+
"""Wrap bare IPv6 addresses in brackets for use in URLs."""
|
|
121
|
+
try:
|
|
122
|
+
addr = ipaddress.ip_address(hostname)
|
|
123
|
+
if addr.version == 6:
|
|
124
|
+
return f"[{hostname}]"
|
|
125
|
+
except ValueError:
|
|
126
|
+
pass
|
|
127
|
+
return hostname
|
|
128
|
+
|
|
129
|
+
|
|
119
130
|
def _is_ip_endpoint(endpoint: str) -> bool:
|
|
120
131
|
"""Return True if the endpoint hostname is an IP address (not a domain)."""
|
|
121
132
|
hostname = urlparse(endpoint).hostname
|
|
@@ -344,23 +355,100 @@ def show(
|
|
|
344
355
|
node_id = _resolve_node(client, alias)
|
|
345
356
|
response = client.get(f"/nodes/{node_id}")
|
|
346
357
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
358
|
+
if response.status_code == 404:
|
|
359
|
+
console.print(f"[red]Node not found:[/red] {alias}")
|
|
360
|
+
raise typer.Exit(1)
|
|
361
|
+
if response.status_code != 200:
|
|
362
|
+
console.print(f"[red]Error:[/red] {response.status_code} - {response.text}")
|
|
363
|
+
raise typer.Exit(1)
|
|
353
364
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
node = response.json()
|
|
366
|
+
console.print(f"[bold]ID:[/bold] {node['id']}")
|
|
367
|
+
console.print(f"[bold]Alias:[/bold] {node.get('alias') or '-'}")
|
|
368
|
+
console.print(f"[bold]Chain:[/bold] {node['chain']}")
|
|
369
|
+
console.print(f"[bold]Status:[/bold] {node['status']}")
|
|
370
|
+
console.print(f"[bold]Endpoint:[/bold] {node['endpoint']}")
|
|
371
|
+
console.print(f"[bold]Created:[/bold] {format_timestamp(node['created_at'])}")
|
|
372
|
+
console.print(
|
|
373
|
+
f"[bold]Last Seen:[/bold] {format_timestamp(node.get('last_seen_at'))}"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
m_resp = client.get(f"/nodes/{node_id}/metrics")
|
|
377
|
+
if m_resp.status_code == 200:
|
|
378
|
+
m = m_resp.json()
|
|
379
|
+
if m.get("available"):
|
|
380
|
+
console.print()
|
|
381
|
+
console.print("[bold]Routing Metrics[/bold]")
|
|
382
|
+
_print_metrics(m)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _fmt_success_rate(v: float) -> str:
|
|
386
|
+
pct = v * 100
|
|
387
|
+
color = "green" if pct >= 99 else "yellow" if pct >= 95 else "red"
|
|
388
|
+
return f"[{color}]{pct:.1f}%[/{color}]"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
_METRIC_FIELDS: list[tuple[str, str, Callable]] = [
|
|
392
|
+
("request_count", "Requests", lambda v: f"{v:,}"),
|
|
393
|
+
("success_rate", "Success Rate", _fmt_success_rate),
|
|
394
|
+
("p95_ms", "P95 Latency", lambda v: f"{v:.0f} ms"),
|
|
395
|
+
("latency_score", "Latency Score", lambda v: f"{v:.4f}"),
|
|
396
|
+
("routing_score", "Routing Score", lambda v: f"{v:.4f}"),
|
|
397
|
+
("traffic_pct", "Traffic Share", lambda v: f"{v:.1f}%"),
|
|
398
|
+
("reliability", "Reliability", lambda v: f"{v:.4f}"),
|
|
399
|
+
("epochs_good", "Epochs Good", str),
|
|
400
|
+
("bid", "Bid", lambda v: f"${v:.6f}"),
|
|
401
|
+
("cohort_median", "Cohort Median", lambda v: f"${v:.6f}"),
|
|
402
|
+
("price_factor", "Price Factor", lambda v: f"{v:.4f}"),
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _print_metrics(m: dict) -> None:
|
|
407
|
+
"""Print routing metrics from a metrics API response."""
|
|
408
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
409
|
+
table.add_column("Key", style="bold")
|
|
410
|
+
table.add_column("Value")
|
|
411
|
+
|
|
412
|
+
for key, label, fmt in _METRIC_FIELDS:
|
|
413
|
+
val = m.get(key)
|
|
414
|
+
if val is not None:
|
|
415
|
+
table.add_row(label, fmt(val))
|
|
416
|
+
|
|
417
|
+
console.print(table)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@app.command("metrics")
|
|
421
|
+
def metrics(
|
|
422
|
+
alias: str = typer.Argument(None, help="Node alias or ID"),
|
|
423
|
+
) -> None:
|
|
424
|
+
"""Show routing metrics for a miner node."""
|
|
425
|
+
alias = _get_alias(alias)
|
|
426
|
+
config = load_config()
|
|
427
|
+
|
|
428
|
+
with RegistryClient(config) as client:
|
|
429
|
+
node_id = _resolve_node(client, alias)
|
|
430
|
+
response = client.get(f"/nodes/{node_id}/metrics")
|
|
431
|
+
|
|
432
|
+
if response.status_code == 503:
|
|
433
|
+
console.print(
|
|
434
|
+
"[yellow]Metrics not available (scoring not enabled)[/yellow]"
|
|
435
|
+
)
|
|
436
|
+
raise typer.Exit(1)
|
|
437
|
+
if response.status_code != 200:
|
|
438
|
+
console.print(f"[red]Error:[/red] {response.status_code} - {response.text}")
|
|
439
|
+
raise typer.Exit(1)
|
|
440
|
+
|
|
441
|
+
m = response.json()
|
|
442
|
+
if not m.get("available"):
|
|
443
|
+
console.print(
|
|
444
|
+
f"[yellow]No metrics available for {alias}[/yellow]\n"
|
|
445
|
+
"[dim]The gateway may not have routed traffic to this"
|
|
446
|
+
" node yet.[/dim]"
|
|
447
|
+
)
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
console.print(f"[bold]Routing Metrics: {alias}[/bold]\n")
|
|
451
|
+
_print_metrics(m)
|
|
364
452
|
|
|
365
453
|
|
|
366
454
|
@app.command("rm")
|
|
@@ -443,7 +531,7 @@ def _test_health(hostname: str) -> tuple[bool, str]:
|
|
|
443
531
|
"""Test HTTP health endpoint on port 80."""
|
|
444
532
|
try:
|
|
445
533
|
r = httpx.get(
|
|
446
|
-
f"http://{hostname}/health",
|
|
534
|
+
f"http://{_bracket_ipv6(hostname)}/health",
|
|
447
535
|
timeout=10,
|
|
448
536
|
follow_redirects=True,
|
|
449
537
|
)
|
|
@@ -458,7 +546,7 @@ def _test_rpc(
|
|
|
458
546
|
hostname: str, port: int, secret: str
|
|
459
547
|
) -> tuple[bool, Optional[float], str]:
|
|
460
548
|
"""Send system_health RPC over HTTPS. Return (ok, latency_ms, message)."""
|
|
461
|
-
url = f"https://{hostname}:{port}"
|
|
549
|
+
url = f"https://{_bracket_ipv6(hostname)}:{port}"
|
|
462
550
|
payload = {
|
|
463
551
|
"jsonrpc": "2.0",
|
|
464
552
|
"method": "system_health",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|