feat(alpha): complete hardening backlog
Complete the alpha-hardening Ralph task set, including tracker billing/accounting guards, validator fraud-audit primitives, wallet binding proof support, documentation runbooks, and updated tests. Verification: .venv/bin/python -m compileall -q packages tests; .venv/bin/python -m pytest -q --tb=short (313 passed, 3 skipped, 1 failed: tests/test_mining_cli.py::test_legacy_start_without_port_uses_next_available_port because meshnet-node pid 1263451 is already listening on port 7000).
This commit is contained in:
@@ -19,7 +19,7 @@ import time
|
||||
import uuid
|
||||
|
||||
DEFAULT_PRICE_PER_1K_TOKENS = 0.02 # USDT
|
||||
DEFAULT_STARTING_CREDIT = 1.0 # USDT of Caller Credit for a new API key
|
||||
DEFAULT_STARTING_CREDIT = 0.0 # USDT; accounts must be funded before inference
|
||||
DEFAULT_BILLING_DB_PATH = "billing.sqlite"
|
||||
NODE_REVENUE_SHARE = 0.90 # nodes 90% / protocol cut 10% (ADR-0015)
|
||||
|
||||
@@ -71,9 +71,9 @@ class BillingLedger:
|
||||
# ---- local operations (create + apply + log an event) ----
|
||||
|
||||
def ensure_client(self, api_key: str) -> float:
|
||||
"""Return the client's balance, granting Caller Credit on first touch."""
|
||||
"""Return the client's balance without granting implicit credit."""
|
||||
with self._lock:
|
||||
if api_key not in self._client_balances:
|
||||
if api_key not in self._client_balances and self._starting_credit > 0.0:
|
||||
self._apply_locked({
|
||||
"id": f"credit-{uuid.uuid4().hex}",
|
||||
"type": "credit",
|
||||
@@ -82,7 +82,7 @@ class BillingLedger:
|
||||
"ts": time.time(),
|
||||
"note": "caller-credit",
|
||||
})
|
||||
return self._client_balances[api_key]
|
||||
return self._client_balances.get(api_key, 0.0)
|
||||
|
||||
def has_funds(self, api_key: str) -> bool:
|
||||
return self.ensure_client(api_key) > 0.0
|
||||
@@ -127,7 +127,7 @@ class BillingLedger:
|
||||
shares[wallet] = shares.get(wallet, 0.0) + node_pool * work / total_work
|
||||
protocol_amount = cost - sum(shares.values())
|
||||
with self._lock:
|
||||
if api_key not in self._client_balances:
|
||||
if api_key not in self._client_balances and self._starting_credit > 0.0:
|
||||
self._apply_locked({
|
||||
"id": f"credit-{uuid.uuid4().hex}",
|
||||
"type": "credit",
|
||||
@@ -150,14 +150,21 @@ class BillingLedger:
|
||||
self._apply_locked(event)
|
||||
return event
|
||||
|
||||
def bind_wallet(self, api_key: str, wallet: str) -> dict:
|
||||
"""Bind a client wallet pubkey to an API key (US-032 deposits)."""
|
||||
def bind_wallet(self, api_key: str, wallet: str, *, admin_override: bool = False) -> dict:
|
||||
"""Bind a client wallet pubkey to an API key (US-032 deposits, C6).
|
||||
|
||||
A wallet already bound to a *different* api_key is left untouched
|
||||
unless ``admin_override`` is set — enforced in ``_apply_locked`` so a
|
||||
conflicting gossip ``bind`` event can never silently clobber an
|
||||
existing binding either. Callers should check ``event["rejected"]``.
|
||||
"""
|
||||
with self._lock:
|
||||
event = {
|
||||
"id": f"bind-{uuid.uuid4().hex}",
|
||||
"type": "bind",
|
||||
"api_key": api_key,
|
||||
"wallet": wallet,
|
||||
"admin_override": admin_override,
|
||||
"ts": time.time(),
|
||||
}
|
||||
self._apply_locked(event)
|
||||
@@ -262,10 +269,17 @@ class BillingLedger:
|
||||
]
|
||||
|
||||
def settle_node_payout(self, wallet: str, amount: float, *, reference: str = "") -> dict:
|
||||
"""Deduct a paid-out amount from a node's pending balance (US-033 hook)."""
|
||||
"""Deduct a paid-out amount from a node's pending balance (US-033 hook).
|
||||
|
||||
ADR-0015: clamps to whatever is still pending under the same lock as
|
||||
the deduction, so a forfeiture that lands between the settlement
|
||||
loop's `payables()` snapshot and this call is never paid out on top
|
||||
of — the wallet gets at most what remains, never a stale amount.
|
||||
"""
|
||||
if amount <= 0:
|
||||
raise ValueError("payout amount must be positive")
|
||||
with self._lock:
|
||||
amount = min(amount, self._node_pending.get(wallet, 0.0))
|
||||
event = {
|
||||
"id": f"payout-{uuid.uuid4().hex}",
|
||||
"type": "payout",
|
||||
@@ -348,7 +362,12 @@ class BillingLedger:
|
||||
self._node_pending[wallet] = self._node_pending.get(wallet, 0.0) - amount
|
||||
self._protocol_cut += amount
|
||||
elif etype == "bind":
|
||||
self._wallet_bindings[event["wallet"]] = event["api_key"]
|
||||
wallet = event["wallet"]
|
||||
existing = self._wallet_bindings.get(wallet)
|
||||
if existing is not None and existing != event["api_key"] and not event.get("admin_override"):
|
||||
event["rejected"] = True
|
||||
else:
|
||||
self._wallet_bindings[wallet] = event["api_key"]
|
||||
else:
|
||||
return
|
||||
self._seen_event_ids.add(event["id"])
|
||||
|
||||
Reference in New Issue
Block a user