feat: add node startup flow

This commit is contained in:
Dobromir Popov
2026-06-29 01:30:12 +03:00
parent 15897d5c95
commit 0199c99f6b
9 changed files with 727 additions and 11 deletions

View File

@@ -0,0 +1,154 @@
"""Full node startup sequence — self-configuring, non-interactive."""
from __future__ import annotations
import json
import socket
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from .downloader import download_shard
from .hardware import detect_hardware
from .server import StubNodeServer
from .wallet import load_or_create_wallet
def _post_json(url: str, payload: dict, timeout: float = 10.0) -> dict:
data = json.dumps(payload).encode()
req = urllib.request.Request(
url, data=data, headers={"Content-Type": "application/json"}, method="POST"
)
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
def _get_json(url: str, timeout: float = 10.0) -> dict:
with urllib.request.urlopen(url, timeout=timeout) as r:
return json.loads(r.read())
def run_startup(
tracker_url: str,
port: int = 0,
model: str = "stub-model",
wallet_path: Path | None = None,
cache_dir: Path | None = None,
host: str = "127.0.0.1",
advertise_host: str | None = None,
) -> StubNodeServer:
"""Execute the full startup sequence and return a running node server.
Steps (all non-interactive):
1. Detect GPU / hardware profile
2. Load or generate Solana wallet keypair
3. Query tracker for optimal shard assignment
4. Download (or stub) the assigned shard
5. Start local HTTP server
6. Register with tracker
Prints a compact status summary on completion.
"""
tracker_url = tracker_url.rstrip("/")
# 1. Hardware detection
print("Detecting hardware...", flush=True)
hw = detect_hardware()
device: str = hw["device"]
gpu_name: str | None = hw.get("gpu_name")
vram_mb: int = hw.get("vram_mb", 0)
if device == "cpu":
print(" WARNING: No CUDA GPU detected — running in CPU mode", flush=True)
else:
print(f" GPU: {gpu_name} ({vram_mb} MB VRAM)", flush=True)
# 2. Wallet
print("Loading wallet...", flush=True)
wallet_kwargs: dict = {}
if wallet_path is not None:
wallet_kwargs["path"] = wallet_path
_, _, address = load_or_create_wallet(**wallet_kwargs)
print(f" Wallet: {address}", flush=True)
# 3. Shard assignment from tracker
print("Querying tracker for shard assignment...", flush=True)
assign_qs = urllib.parse.urlencode({
"model": model,
"device": device,
"vram_mb": vram_mb,
})
try:
assignment = _get_json(f"{tracker_url}/v1/nodes/assign?{assign_qs}")
except urllib.error.URLError as exc:
print(f" ERROR: Cannot reach tracker at {tracker_url}: {exc}", file=sys.stderr, flush=True)
raise
shard_start: int = assignment["shard_start"]
shard_end: int = assignment["shard_end"]
assigned_model: str = assignment.get("model", model)
hf_repo: str | None = assignment.get("hf_repo")
print(f" Shard: layers {shard_start}-{shard_end} of {assigned_model}", flush=True)
# 4. Download shard
print("Downloading shard...", flush=True)
dl_kwargs: dict = {}
if cache_dir is not None:
dl_kwargs["cache_dir"] = cache_dir
if hf_repo is not None:
dl_kwargs["hf_repo"] = hf_repo
shard_path = download_shard(assigned_model, shard_start, shard_end, **dl_kwargs)
print(f" Cached at: {shard_path}", flush=True)
# 5. Start HTTP server
is_last = shard_end >= assignment.get("model_layers_end", shard_end)
node = StubNodeServer(
host=host,
port=port,
shard_start=shard_start,
shard_end=shard_end,
is_last_shard=is_last,
)
actual_port = node.start()
public_host = advertise_host or (socket.getfqdn() if host == "0.0.0.0" else host)
endpoint = f"http://{public_host}:{actual_port}"
# 6. Register with tracker
print("Registering with tracker...", flush=True)
try:
reg_resp = _post_json(
f"{tracker_url}/v1/nodes/register",
{
"endpoint": endpoint,
"shard_start": shard_start,
"shard_end": shard_end,
"hardware_profile": hw,
"wallet_address": address,
"score": 1.0,
},
)
node_id: str = reg_resp["node_id"]
except Exception:
node.stop()
raise
# Status summary
hw_str = device.upper()
if gpu_name:
hw_str += f" ({gpu_name}, {vram_mb} MB)"
print(
f"\n{'=' * 32}\n"
f"meshnet-node ready\n"
f" Wallet: {address}\n"
f" Shard: layers {shard_start}-{shard_end} ({assigned_model})\n"
f" Endpoint: {endpoint}\n"
f" Node ID: {node_id}\n"
f" Hardware: {hw_str}\n"
f"{'=' * 32}",
flush=True,
)
return node