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:
@@ -15,7 +15,8 @@ from meshnet_contracts import LocalSolanaContracts
|
||||
from meshnet_node.server import StubNodeServer
|
||||
from meshnet_tracker.billing import BillingLedger
|
||||
from meshnet_tracker.server import TrackerServer
|
||||
from meshnet_validator import ValidatorProcess
|
||||
from meshnet_validator import ToplocAuditConfig, ValidatorProcess
|
||||
from meshnet_validator.audit import build_activation_proofs
|
||||
|
||||
MODEL = "stub-model"
|
||||
|
||||
@@ -261,3 +262,142 @@ def test_probation_earns_nothing_then_earning_begins():
|
||||
finally:
|
||||
stub.stop()
|
||||
tracker.stop()
|
||||
|
||||
|
||||
class _FakeToploc:
|
||||
"""Fingerprint = the activations themselves; verify is exact equality
|
||||
(same contract as tests/test_hop_bisection.py's fake backend)."""
|
||||
|
||||
def build_proofs_base64(self, activations, *, decode_batching_size, topk, skip_prefill):
|
||||
return {
|
||||
"activation_fingerprint": tuple(tuple(row) for row in activations),
|
||||
"decode_batching_size": decode_batching_size,
|
||||
"topk": topk,
|
||||
"skip_prefill": skip_prefill,
|
||||
}
|
||||
|
||||
def verify_proofs_base64(self, activations, proofs, *, decode_batching_size, topk, skip_prefill):
|
||||
return proofs == {
|
||||
"activation_fingerprint": tuple(tuple(row) for row in activations),
|
||||
"decode_batching_size": decode_batching_size,
|
||||
"topk": topk,
|
||||
"skip_prefill": skip_prefill,
|
||||
}
|
||||
|
||||
|
||||
class _HopReferenceValidator(ValidatorProcess):
|
||||
"""Stands in for the reference node: returns canned ground-truth
|
||||
activations at each hop cut-point instead of making an HTTP call."""
|
||||
|
||||
def __init__(self, *args, reference_activations_by_hop, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._reference_activations_by_hop = reference_activations_by_hop
|
||||
|
||||
def _run_teacher_forced_prefill_hops(self, **kwargs):
|
||||
return self._reference_activations_by_hop
|
||||
|
||||
|
||||
def _record_two_hop_event(
|
||||
contracts, session_id: str, *, hop0_activations, hop1_activations, config, backend,
|
||||
) -> None:
|
||||
"""Two-hop route (wallet-hop0 upstream, wallet-hop1 downstream), each hop
|
||||
carrying its own on-demand TOPLOC commitment (AH-007 bisection format)."""
|
||||
hop0_claim = build_activation_proofs(hop0_activations, config=config, backend=backend)
|
||||
hop1_claim = build_activation_proofs(hop1_activations, config=config, backend=backend)
|
||||
token_ids = [101, 202, 303]
|
||||
contracts.validation.record_completed_inference(
|
||||
session_id=session_id,
|
||||
model=MODEL,
|
||||
messages=[{"role": "user", "content": "2+2"}],
|
||||
observed_output="two-hop pipeline output",
|
||||
route_nodes=[
|
||||
{
|
||||
"wallet_address": "wallet-hop0",
|
||||
"shard_start": 0,
|
||||
"shard_end": 15,
|
||||
"toploc_proof": hop0_claim.as_mapping(),
|
||||
"claimed_token_ids": token_ids,
|
||||
},
|
||||
{
|
||||
"wallet_address": "wallet-hop1",
|
||||
"shard_start": 16,
|
||||
"shard_end": 31,
|
||||
"toploc_proof": hop1_claim.as_mapping(),
|
||||
"claimed_token_ids": token_ids,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_60_request_stream_bans_intermittent_first_hop_cheater_not_last_hop():
|
||||
"""Integration (AH-010): a 60-request stream through a two-hop route where
|
||||
the *first* hop (not the last) cheats on 3 of the jobs. TOPLOC bisection
|
||||
(issue 07) must blame wallet-hop0 specifically -- the old last-hop-only
|
||||
heuristic would have blamed the innocent wallet-hop1 instead. Each catch
|
||||
forfeits wallet-hop0's pending balance and strikes it in the same
|
||||
validation cycle; the third strike bans it within the 60-request stream,
|
||||
and the settlement loop's payables() then excludes only the banned wallet
|
||||
while the honest downstream hop keeps earning (ADR-0015/ADR-0018).
|
||||
"""
|
||||
config = ToplocAuditConfig(topk=2, decode_batching_size=16)
|
||||
backend = _FakeToploc()
|
||||
reference_hop0 = [[1.0, 2.0], [3.0, 4.0]]
|
||||
reference_hop1 = [[5.0, 6.0], [7.0, 8.0]]
|
||||
corrupted_hop0 = [[9.0, 9.0], [9.0, 9.0]]
|
||||
cheat_at = {20, 40, 60}
|
||||
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.submit_stake("wallet-hop0", 500)
|
||||
contracts.registry.submit_stake("wallet-hop1", 500)
|
||||
ledger = BillingLedger(starting_credit=1000.0, default_price_per_1k=0.02)
|
||||
|
||||
validator = _HopReferenceValidator(
|
||||
contracts=contracts,
|
||||
billing=ledger,
|
||||
reference_node_url="http://reference.invalid",
|
||||
sample_rate=1.0,
|
||||
strike_threshold=3,
|
||||
random_seed=7,
|
||||
toploc_config=config,
|
||||
toploc_backend=backend,
|
||||
reference_activations_by_hop=[reference_hop0, reference_hop1],
|
||||
)
|
||||
|
||||
banned_at: int | None = None
|
||||
for i in range(1, 61):
|
||||
ledger.charge_request(
|
||||
"client", MODEL, 1000, [("wallet-hop0", 6), ("wallet-hop1", 6)],
|
||||
)
|
||||
_record_two_hop_event(
|
||||
contracts,
|
||||
f"sess-{i}",
|
||||
hop0_activations=corrupted_hop0 if i in cheat_at else reference_hop0,
|
||||
hop1_activations=reference_hop1,
|
||||
config=config,
|
||||
backend=backend,
|
||||
)
|
||||
validator.validate_once()
|
||||
if banned_at is None and contracts.registry.get_wallet("wallet-hop0").banned:
|
||||
banned_at = i
|
||||
|
||||
assert banned_at is not None and banned_at <= 60
|
||||
cheater = contracts.registry.get_wallet("wallet-hop0")
|
||||
honest = contracts.registry.get_wallet("wallet-hop1")
|
||||
assert cheater.strike_count == 3
|
||||
assert cheater.banned
|
||||
assert not honest.banned
|
||||
# Every catch blamed the true (first, non-last) culprit hop -- never the
|
||||
# innocent downstream wallet.
|
||||
assert ledger.get_node_pending("wallet-hop0") == pytest.approx(0.0)
|
||||
assert ledger.get_node_pending("wallet-hop1") > 0.0
|
||||
|
||||
# Settlement-loop wiring (server.py `_settlement_loop`): banned wallets
|
||||
# are excluded from payables even though their (forfeited-to-zero)
|
||||
# balance would fail the dust floor anyway -- the honest hop is still due.
|
||||
banned_wallets = {
|
||||
wallet for wallet in ("wallet-hop0", "wallet-hop1")
|
||||
if contracts.registry.get_wallet(wallet).banned
|
||||
}
|
||||
due = dict(ledger.payables(threshold=0.0, max_period=0.0, dust_floor=0.0, exclude=banned_wallets))
|
||||
assert "wallet-hop0" not in due
|
||||
assert due.get("wallet-hop1", 0.0) > 0.0
|
||||
|
||||
Reference in New Issue
Block a user