49 lines
4.6 KiB
Python
49 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""The wave's wind-down by the standing rule (6 October 2026, 21:5xZ, the coordinator's order): never remove more than 10 percent of
|
|
the live devnet's voter weight in any hour, so finality never sees tonight's departure again. Every hour: read each candidate's
|
|
share of the last 2,000 blocks (lib.standing.weight_shares, the conservative proxy for the table), pick the largest slice under
|
|
LIMIT (default 9.5 percent) that weight_check allows (it adds what the last hour removed), hold the slice if the hub's last lock
|
|
signed under HOLD_PCT of the table after the removal, destroy the slice's pods (RunPod, registry rows marked), then read the lock
|
|
line ten minutes later. Candidates: the wave pods, then the 8x rig (one voter), never a standing box or a Devnet 2 box; rz-4090 waits
|
|
for the proving agent's word. Log: ~/Desktop/fleet/winddown.log; schedule and finality reads in ~/Desktop/fleet/winddown.jsonl."""
|
|
import sys, os, time, json, datetime
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))); from lib import Box, Registry, standing
|
|
LIMIT = float(os.environ.get("LIMIT", "0.095")); HOLD_PCT = float(os.environ.get("HOLD_PCT", "66.7")); EVERY = int(os.environ.get("EVERY", "3600"))
|
|
ROOT = os.path.expanduser("~/Desktop/fleet"); J = os.path.join(ROOT, "winddown.jsonl")
|
|
def st(): return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
def log(**row): row["t"] = st(); open(J, "a").write(json.dumps(row) + "\n"); print(row["t"], {k: v for k, v in row.items() if k != "t"}, flush=True)
|
|
hub = Box.from_registry("hub-1")
|
|
def finality():
|
|
rc, out, err = hub.run("grep -E 'Finality: checkpoint [0-9]+ LOCKED:' /root/fleet/node.log | tail -1 | grep -oE 'checkpoint [0-9]+|signed [0-9]+ = [0-9.]+% of active, [0-9.]+% of total' | tr '\\n' ' '; grep -E 'certificate at index [0-9]+ received' /root/fleet/node.log | tail -1 | grep -oE '[0-9]+ of [0-9]+ voters, weight [0-9]+ of [0-9]+'; grep -E 'Finality: checkpoint [0-9]+ LOCKED:' /root/fleet/node.log | tail -1 | cut -c1-19", 40)
|
|
s = out.replace("\n", " | ").strip(); import re; m = re.search(r"([0-9.]+)% of total", s); return s, float(m.group(1)) if m else None
|
|
def candidates():
|
|
reg = Registry.load(); pods = sorted((i, b) for i, b in reg.items() if b.get("state") != "destroyed" and b.get("ssh_host") and b["label"].startswith("wave"))
|
|
rig = [(i, b) for i, b in reg.items() if b.get("state") != "destroyed" and b.get("label") == "rig-4090x8"]
|
|
return pods + rig
|
|
def keys_of(labels):
|
|
rc, out, err = hub.run("for l in " + " ".join(labels) + "; do echo $l $(/opt/igneum/pkg/bin/igneum-miner key-hash $l 2>/dev/null | grep -oE '[0-9a-f]{64}' | head -1); done", 120)
|
|
return dict(ln.split() for ln in out.splitlines() if len(ln.split()) == 2)
|
|
while True:
|
|
cands = candidates()
|
|
if not cands: log(event="done", note="no candidates left: the wave and the rig are down; the standing 14 and the Devnet 2 four stay"); break
|
|
labels = [b["label"] for _, b in cands]; sh, n = standing.weight_shares(2000); keys = keys_of(labels); share = {l: sh.get(keys.get(l, ""), 0.0) for l in labels}
|
|
fin, signed_total = finality()
|
|
recent = 0.0
|
|
if os.path.exists(standing.WEIGHT_LOG):
|
|
for ln in open(standing.WEIGHT_LOG):
|
|
try: j = json.loads(ln)
|
|
except Exception: continue
|
|
if j.get("ts", 0) >= time.time() - 3600: recent += float(j.get("share", 0))
|
|
budget = LIMIT - recent; slice_, acc = [], 0.0
|
|
for l in sorted(labels, key=lambda x: share[x]): # smallest shares first: more pods per slice, the same weight
|
|
if acc + share[l] <= budget: slice_.append(l); acc += share[l]
|
|
if signed_total is not None and signed_total - 100 * acc < HOLD_PCT: log(event="hold", reason=f"the slice would take the signed share from {signed_total}% to {signed_total - 100*acc:.1f}%, under {HOLD_PCT}%", finality=fin); time.sleep(EVERY); continue
|
|
if not slice_: log(event="hold", reason=f"no pod fits the budget ({100*budget:.1f}% left this hour)", finality=fin); time.sleep(EVERY); continue
|
|
v = standing.weight_check(slice_, limit=LIMIT)
|
|
if not v["ok"]: log(event="hold", reason="weight_check refused", verdict=v, finality=fin); time.sleep(EVERY); continue
|
|
log(event="slice", pods=slice_, share_pct=round(100 * acc, 2), remaining=len(labels) - len(slice_), finality_before=fin, blocks_read=n)
|
|
for l in slice_:
|
|
try: Box.from_registry(l).destroy(); log(event="destroyed", pod=l, share_pct=round(100 * share[l], 2))
|
|
except Exception as e: log(event="destroy_failed", pod=l, error=str(e)[:120])
|
|
time.sleep(600); fin2, _ = finality(); log(event="finality_after", slice=slice_, finality=fin2)
|
|
time.sleep(EVERY - 600)
|