test
This commit is contained in:
128
tests/test_registry_persistence.py
Normal file
128
tests/test_registry_persistence.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""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():
|
||||
wallet = RegistryWallet()
|
||||
assert wallet.reputation == 1.0
|
||||
assert wallet.last_audit_ts is None
|
||||
|
||||
|
||||
# ---- persistence across restart ----
|
||||
|
||||
|
||||
def test_strike_survives_restart(tmp_path):
|
||||
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):
|
||||
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):
|
||||
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():
|
||||
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):
|
||||
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()
|
||||
Reference in New Issue
Block a user