Standing fleet: weight_check reads vote-key shares from the hub's inspect and maps boxes by key-hash (tested: two boxes = 1.2 percent of 2,000 blocks)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-06 20:03:41 +00:00
parent 796626af06
commit 9d24ad323e

View file

@ -91,27 +91,28 @@ def rerent(label):
return new
# ---- the 10 percent rule (20:00Z): never remove more than 10 percent of the live devnet's 30-day weight in any hour ----
WEIGHT_LOG = os.path.join(ROOT, "weight-removals.jsonl")
def weight_shares(window_blocks=30*86400):
"""Blue blocks per payout label over the window, from the hub's node (igneum-miner payouts); returns {label: share}."""
def weight_shares(blocks=2000):
"""Vote-key-hash shares over the last `blocks` blocks of the live devnet, from the hub's node (igneum-miner inspect:
one line per block with voteKeyHash=<64 hex>); the hub is pruned, so the 30-day window is approximated by the largest
read the RPC serves (2,000 blocks is about 33 minutes at 1 block/s; pass more when the RPC allows). Returns
({key_hash: share}, blocks_read)."""
hub = next((v for v in Registry.load().values() if v.get("hub") and v.get("state") != "destroyed"), None)
if not hub: raise SshError("no hub box")
b = Box(hub["ssh_host"], hub["ssh_port"], "hub-1", None, None, hub.get("provider"))
rc, out, err = b.run("/opt/igneum/pkg/bin/igneum-miner payouts grpc://127.0.0.1:26610 2>/dev/null | tail -400", 120)
rows = {}
for ln in out.splitlines():
parts = ln.split()
nums = [p for p in parts if p.isdigit()]
if len(parts) >= 2 and nums:
rows[parts[0]] = rows.get(parts[0], 0) + int(nums[-1])
tot = sum(rows.values()) or 1
return {k: v / tot for k, v in rows.items()}, out[-600:]
rc, out, err = b.run(f"timeout 170 /opt/igneum/pkg/bin/igneum-miner inspect {blocks} grpc://127.0.0.1:26610 2>&1 | grep -oE 'voteKeyHash=[0-9a-f]{{64}}'", 190)
keys = [ln.split("=", 1)[1] for ln in out.split() if ln.startswith("voteKeyHash=")]
n = len(keys) or 1; rows = {}
for k in keys: rows[k] = rows.get(k, 0) + 1
return {k: v / n for k, v in rows.items()}, len(keys)
def vote_key_hash(label):
"""The box's vote key hash: its miner votes with the key its payout label derives (igneum-miner key-hash <label>)."""
b = Box.from_registry(label); rc, out, err = b.run(f"/opt/igneum/pkg/bin/igneum-miner key-hash {label} 2>/dev/null | grep -oE '[0-9a-f]{{64}}' | head -1", 30)
return out.strip()
def weight_check(labels, limit=0.10, hours=1.0):
"""Refuses when the labels' weight plus what was removed in the last `hours` exceeds `limit`. Records an allowed removal."""
shares, raw = weight_shares()
reg = Registry.load(); keys = {}
for l in labels:
iid, b = Registry.find(l); keys[l] = (b.get("wallet") or "").lower()
want = sum(shares.get(k, 0) for k in keys.values()) + sum(shares.get(l, 0) for l in labels)
shares, nblocks = weight_shares()
keys = {l: vote_key_hash(l) for l in labels}
want = sum(shares.get(k, 0) for k in keys.values() if k)
since = time.time() - hours * 3600; recent = 0.0
if os.path.exists(WEIGHT_LOG):
for ln in open(WEIGHT_LOG):
@ -119,7 +120,7 @@ def weight_check(labels, limit=0.10, hours=1.0):
except Exception: continue
if j.get("ts", 0) >= since: recent += float(j.get("share", 0))
ok = (want + recent) <= limit
verdict = {"labels": labels, "share": round(want, 4), "removed_last_hour": round(recent, 4), "limit": limit, "ok": ok, "t": now()}
verdict = {"labels": labels, "keys": {l: k[:16] for l, k in keys.items()}, "share": round(want, 4), "removed_last_hour": round(recent, 4), "limit": limit, "ok": ok, "blocks_read": nblocks, "t": now()}
if ok:
with open(WEIGHT_LOG, "a") as f: f.write(json.dumps({"ts": time.time(), "labels": labels, "share": want}) + "\n")
return verdict
@ -152,4 +153,4 @@ if __name__ == "__main__":
elif a[0] == "weight_check":
v = weight_check(a[1:]); print(json.dumps(v)); sys.exit(0 if v["ok"] else 1)
elif a[0] == "weights":
sh, raw = weight_shares(); print(json.dumps(dict(sorted(sh.items(), key=lambda kv: -kv[1])[:40]), indent=1))
sh, n = weight_shares(); print("blocks read", n, "voters", len(sh)); print(json.dumps({k[:16]: round(v, 4) for k, v in sorted(sh.items(), key=lambda kv: -kv[1])[:40]}, indent=1))