"""US-007 integration tests: optimistic fraud detection and slashing.""" import json import urllib.error import urllib.request from meshnet_contracts import LocalSolanaContracts from meshnet_gateway.server import GatewayServer from meshnet_node.server import StubNodeServer from meshnet_tracker.server import TrackerServer from meshnet_validator import ValidatorProcess def _post_json(url: str, payload: dict, headers: dict | None = None) -> dict: req = urllib.request.Request( url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json", **(headers or {})}, method="POST", ) with urllib.request.urlopen(req) as resp: return json.loads(resp.read()) def _register_node( tracker_url: str, endpoint: str, shard_start: int, shard_end: int, wallet_address: str, ) -> None: _post_json( f"{tracker_url}/v1/nodes/register", { "endpoint": endpoint, "shard_start": shard_start, "shard_end": shard_end, "hardware_profile": {}, "wallet_address": wallet_address, "score": 1.0, }, ) def _send_completion(gateway_url: str, prompt: str) -> str: body = _post_json( f"{gateway_url}/v1/chat/completions", { "model": "stub-model", "messages": [{"role": "user", "content": prompt}], }, {"Authorization": "Bearer fraud-test-key"}, ) return body["choices"][0]["message"]["content"] def test_bad_node_is_slashed_and_excluded_from_gateway_routes(capsys): "A bad final shard is slashed by the validator and then excluded by routing.\n\nTags: billing, routing, security" contracts = LocalSolanaContracts() contracts.registry.submit_stake("wallet-good", 500) contracts.registry.submit_stake("wallet-bad", 500) tracker = TrackerServer(contracts=contracts) tracker_port = tracker.start() tracker_url = f"http://127.0.0.1:{tracker_port}" good_node = StubNodeServer(shard_start=0, shard_end=15, is_last_shard=False) bad_node = StubNodeServer( shard_start=16, shard_end=31, is_last_shard=True, response_prefix="fraudulent response to:", ) reference_node = StubNodeServer(shard_start=0, shard_end=31, is_last_shard=True) good_port = good_node.start() bad_port = bad_node.start() reference_port = reference_node.start() _register_node(tracker_url, f"http://127.0.0.1:{good_port}", 0, 15, "wallet-good") _register_node(tracker_url, f"http://127.0.0.1:{bad_port}", 16, 31, "wallet-bad") gateway = GatewayServer(tracker_url=tracker_url, contracts=contracts) gateway_port = gateway.start() gateway_url = f"http://127.0.0.1:{gateway_port}" validator = ValidatorProcess( contracts=contracts, reference_node_url=f"http://127.0.0.1:{reference_port}", sample_rate=1.0, slash_amount=125, strike_threshold=1, random_seed=7, ) try: for i in range(20): assert _send_completion(gateway_url, f"prompt {i}").startswith("fraudulent response to:") receipts = validator.validate_once() bad_wallet = contracts.registry.get_wallet("wallet-bad") assert receipts assert bad_wallet.stake_balance == 375 assert bad_wallet.strike_count == 1 assert bad_wallet.banned is True assert "WARNING: node wallet-bad slashed" in capsys.readouterr().out try: _send_completion(gateway_url, "after slash") raise AssertionError("Expected no route after bad wallet is excluded") except urllib.error.HTTPError as exc: assert exc.code == 503 finally: gateway.stop() reference_node.stop() bad_node.stop() good_node.stop() tracker.stop() def test_validator_sampling_rate_is_configurable(): "The validator only reruns requests selected by its configured sample rate.\n\nTags: billing, security" contracts = LocalSolanaContracts() class InProcessReferenceValidator(ValidatorProcess): def _run_reference(self, messages: list[dict]) -> str: return f"stub response to: {messages[-1]['content']}" validator = InProcessReferenceValidator( contracts=contracts, reference_node_url="http://reference-node.invalid", sample_rate=0.05, random_seed=1, ) for i in range(1_000): contracts.validation.record_completed_inference( session_id=f"session-{i}", model="stub-model", messages=[{"role": "user", "content": f"prompt {i}"}], observed_output=f"stub response to: prompt {i}", route_nodes=[{"wallet_address": "wallet-good"}], ) validator.validate_once() assert 30 <= validator.sampled_count <= 70