"""Issue 05 (A1/A5) tests: strike/ban/reputation state survives tracker restart. Registry wallet state was RAM-only (`_LocalContractState.registry`) — a tracker restart wiped strikes, bans, and job counts. These tests pin the event-sourced SQLite persistence (same replication model as BillingLedger / AccountStore) per ADR-0016 §4 and ADR-0018 §6. """ import json import urllib.error import urllib.request import pytest from meshnet_contracts import LocalSolanaContracts, RegistryWallet from meshnet_tracker.server import TrackerServer def _post_json(url: str, payload: dict) -> 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) as r: return json.loads(r.read()) # ---- schema: new reputation fields ---- def test_registry_wallet_has_reputation_fields(): "Registry wallet has reputation fields\n\nTags: security, wallet" wallet = RegistryWallet() assert wallet.reputation == 1.0 assert wallet.last_audit_ts is None # ---- persistence across restart ---- def test_strike_survives_restart(tmp_path): "Strike survives restart\n\nTags: general" db = str(tmp_path / "registry.sqlite") contracts = LocalSolanaContracts(registry_db=db) contracts.registry.record_strike("wallet-a") contracts.save_to_db() reopened = LocalSolanaContracts(registry_db=db) assert reopened.registry.get_wallet("wallet-a").strike_count == 1 def test_ban_stake_and_jobs_survive_restart(tmp_path): "Ban stake and jobs survive restart\n\nTags: general" db = str(tmp_path / "registry.sqlite") contracts = LocalSolanaContracts(registry_db=db) contracts.registry.submit_stake("wallet-b", 500) contracts.registry.submit_slash_proof( wallet_address="wallet-b", slash_amount=200, strike_threshold=1 ) contracts.registry.record_completed_job("wallet-b") contracts.save_to_db() reopened = LocalSolanaContracts(registry_db=db) wallet = reopened.registry.get_wallet("wallet-b") assert wallet.banned is True assert wallet.strike_count == 1 assert wallet.stake_balance == 300 assert wallet.completed_job_count == 1 # list_wallets must reflect persisted state assert "wallet-b" in reopened.registry.list_wallets() def test_reputation_and_audit_ts_survive_restart(tmp_path): "Reputation and audit ts survive restart\n\nTags: general" db = str(tmp_path / "registry.sqlite") contracts = LocalSolanaContracts(registry_db=db) contracts.registry.set_reputation("wallet-c", 0.8) contracts.registry.record_audit("wallet-c", ts=1234.5) contracts.save_to_db() reopened = LocalSolanaContracts(registry_db=db) wallet = reopened.registry.get_wallet("wallet-c") assert wallet.reputation == pytest.approx(0.8) assert wallet.last_audit_ts == pytest.approx(1234.5) # ---- event replication (multi-tracker gossip / future Raft) ---- def test_registry_events_replicate_between_instances(): "Registry events replicate between instances\n\nTags: general" a = LocalSolanaContracts() b = LocalSolanaContracts() a.registry.record_strike("wallet-d") a.registry.submit_stake("wallet-d", 100) events, cursor = a.registry_log.events_since(0) assert cursor == len(events) >= 2 applied = b.registry_log.apply_events(events) assert applied == len(events) assert b.registry.get_wallet("wallet-d") == a.registry.get_wallet("wallet-d") # idempotent: re-applying the same events is a no-op assert b.registry_log.apply_events(events) == 0 # ---- banned wallet rejected at registration after restart ---- def test_banned_wallet_rejected_at_registration_after_restart(tmp_path): "Banned wallet rejected at registration after restart\n\nTags: security, wallet" db = str(tmp_path / "registry.sqlite") contracts = LocalSolanaContracts(registry_db=db) contracts.registry.ban_wallet("wallet-evil") contracts.save_to_db() tracker = TrackerServer(contracts=LocalSolanaContracts(registry_db=db)) port = tracker.start() try: with pytest.raises(urllib.error.HTTPError) as excinfo: _post_json( f"http://127.0.0.1:{port}/v1/nodes/register", { "endpoint": "http://127.0.0.1:7900", "model": "test-model", "num_layers": 4, "shard_start": 0, "shard_end": 3, "wallet_address": "wallet-evil", }, ) assert excinfo.value.code == 403 finally: tracker.stop()