nexaroa 0.0.111__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. neuroshard/__init__.py +93 -0
  2. neuroshard/__main__.py +4 -0
  3. neuroshard/cli.py +466 -0
  4. neuroshard/core/__init__.py +92 -0
  5. neuroshard/core/consensus/verifier.py +252 -0
  6. neuroshard/core/crypto/__init__.py +20 -0
  7. neuroshard/core/crypto/ecdsa.py +392 -0
  8. neuroshard/core/economics/__init__.py +52 -0
  9. neuroshard/core/economics/constants.py +387 -0
  10. neuroshard/core/economics/ledger.py +2111 -0
  11. neuroshard/core/economics/market.py +975 -0
  12. neuroshard/core/economics/wallet.py +168 -0
  13. neuroshard/core/governance/__init__.py +74 -0
  14. neuroshard/core/governance/proposal.py +561 -0
  15. neuroshard/core/governance/registry.py +545 -0
  16. neuroshard/core/governance/versioning.py +332 -0
  17. neuroshard/core/governance/voting.py +453 -0
  18. neuroshard/core/model/__init__.py +30 -0
  19. neuroshard/core/model/dynamic.py +4186 -0
  20. neuroshard/core/model/llm.py +905 -0
  21. neuroshard/core/model/registry.py +164 -0
  22. neuroshard/core/model/scaler.py +387 -0
  23. neuroshard/core/model/tokenizer.py +568 -0
  24. neuroshard/core/network/__init__.py +56 -0
  25. neuroshard/core/network/connection_pool.py +72 -0
  26. neuroshard/core/network/dht.py +130 -0
  27. neuroshard/core/network/dht_plan.py +55 -0
  28. neuroshard/core/network/dht_proof_store.py +516 -0
  29. neuroshard/core/network/dht_protocol.py +261 -0
  30. neuroshard/core/network/dht_service.py +506 -0
  31. neuroshard/core/network/encrypted_channel.py +141 -0
  32. neuroshard/core/network/nat.py +201 -0
  33. neuroshard/core/network/nat_traversal.py +695 -0
  34. neuroshard/core/network/p2p.py +929 -0
  35. neuroshard/core/network/p2p_data.py +150 -0
  36. neuroshard/core/swarm/__init__.py +106 -0
  37. neuroshard/core/swarm/aggregation.py +729 -0
  38. neuroshard/core/swarm/buffers.py +643 -0
  39. neuroshard/core/swarm/checkpoint.py +709 -0
  40. neuroshard/core/swarm/compute.py +624 -0
  41. neuroshard/core/swarm/diloco.py +844 -0
  42. neuroshard/core/swarm/factory.py +1288 -0
  43. neuroshard/core/swarm/heartbeat.py +669 -0
  44. neuroshard/core/swarm/logger.py +487 -0
  45. neuroshard/core/swarm/router.py +658 -0
  46. neuroshard/core/swarm/service.py +640 -0
  47. neuroshard/core/training/__init__.py +29 -0
  48. neuroshard/core/training/checkpoint.py +600 -0
  49. neuroshard/core/training/distributed.py +1602 -0
  50. neuroshard/core/training/global_tracker.py +617 -0
  51. neuroshard/core/training/production.py +276 -0
  52. neuroshard/governance_cli.py +729 -0
  53. neuroshard/grpc_server.py +895 -0
  54. neuroshard/runner.py +3223 -0
  55. neuroshard/sdk/__init__.py +92 -0
  56. neuroshard/sdk/client.py +990 -0
  57. neuroshard/sdk/errors.py +101 -0
  58. neuroshard/sdk/types.py +282 -0
  59. neuroshard/tracker/__init__.py +0 -0
  60. neuroshard/tracker/server.py +864 -0
  61. neuroshard/ui/__init__.py +0 -0
  62. neuroshard/ui/app.py +102 -0
  63. neuroshard/ui/templates/index.html +1052 -0
  64. neuroshard/utils/__init__.py +0 -0
  65. neuroshard/utils/autostart.py +81 -0
  66. neuroshard/utils/hardware.py +121 -0
  67. neuroshard/utils/serialization.py +90 -0
  68. neuroshard/version.py +1 -0
  69. nexaroa-0.0.111.dist-info/METADATA +283 -0
  70. nexaroa-0.0.111.dist-info/RECORD +78 -0
  71. nexaroa-0.0.111.dist-info/WHEEL +5 -0
  72. nexaroa-0.0.111.dist-info/entry_points.txt +4 -0
  73. nexaroa-0.0.111.dist-info/licenses/LICENSE +190 -0
  74. nexaroa-0.0.111.dist-info/top_level.txt +2 -0
  75. protos/__init__.py +0 -0
  76. protos/neuroshard.proto +651 -0
  77. protos/neuroshard_pb2.py +160 -0
  78. protos/neuroshard_pb2_grpc.py +1298 -0
@@ -0,0 +1,516 @@
1
+ """
2
+ DHT-Based Proof Storage for Decentralized Balance Sync
3
+
4
+ This module implements a fully decentralized, trustless proof storage system
5
+ using Kademlia DHT. This is the "proper fix" for balance synchronization.
6
+
7
+ Architecture:
8
+ ┌─────────────────────────────────────────────────────────────────┐
9
+ │ 1. PROOF GENERATION │
10
+ │ - Node earns NEURO via PoNW │
11
+ │ - Proof signed with ECDSA │
12
+ │ - Stored locally + gossiped to peers │
13
+ │ - ALSO stored in DHT at SHA256(wallet_id) │
14
+ └─────────────────────────────────────────────────────────────────┘
15
+ ┌─────────────────────────────────────────────────────────────────┐
16
+ │ 2. DHT STORAGE │
17
+ │ - Key: SHA256(wallet_id) → Deterministic location │
18
+ │ - Value: JSON list of proofs (with signatures) │
19
+ │ - Replication: Stored on k=3 closest nodes │
20
+ │ - Persistence: Each node stores ~100 most recent proofs │
21
+ └─────────────────────────────────────────────────────────────────┘
22
+ ┌─────────────────────────────────────────────────────────────────┐
23
+ │ 3. BALANCE BOOTSTRAP (New Node) │
24
+ │ - Calculate DHT key = SHA256(wallet_id) │
25
+ │ - Query DHT for proofs │
26
+ │ - Verify ECDSA signature on EACH proof │
27
+ │ - Cross-validate with 3+ DHT nodes │
28
+ │ - Credit only cryptographically verified proofs │
29
+ └─────────────────────────────────────────────────────────────────┘
30
+
31
+ Security Properties:
32
+ - ✅ Trustless: All proofs have ECDSA signatures
33
+ - ✅ Decentralized: No central server required
34
+ - ✅ Byzantine-resistant: Majority consensus required
35
+ - ✅ Replay-protected: Signature deduplication
36
+ - ✅ Rate-limited: Plausibility checks on claims
37
+ """
38
+
39
+ import hashlib
40
+ import json
41
+ import logging
42
+ import time
43
+ from typing import List, Dict, Optional, Tuple
44
+ from dataclasses import dataclass, asdict
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+
49
+ @dataclass
50
+ class DHTProofRecord:
51
+ """
52
+ A proof record stored in DHT.
53
+
54
+ This contains ALL fields needed for ECDSA signature verification.
55
+ Missing any field = can't verify = REJECT proof.
56
+
57
+ CRITICAL: We store the FULL proof data so we can reconstruct
58
+ canonical_payload for signature verification.
59
+ """
60
+ node_id: str
61
+ timestamp: float
62
+ proof_type: str # "training", "inference", "uptime"
63
+ nonce: str # 🔒 REQUIRED for canonical_payload
64
+ reward: float
65
+ signature: str
66
+ public_key: str # 🔒 REQUIRED for trustless verification
67
+
68
+ # Work metrics (required for canonical_payload)
69
+ uptime_seconds: float = 0.0
70
+ tokens_processed: int = 0
71
+ training_batches: int = 0
72
+ data_samples: int = 0
73
+ model_hash: str = "" # 🔒 REQUIRED for canonical_payload
74
+
75
+ # Role metadata
76
+ layers_held: int = 0
77
+ has_embedding: bool = False
78
+ has_lm_head: bool = False
79
+
80
+ def to_dict(self) -> dict:
81
+ """Convert to dict for JSON storage."""
82
+ return asdict(self)
83
+
84
+ @classmethod
85
+ def from_dict(cls, data: dict) -> 'DHTProofRecord':
86
+ """Create from dict."""
87
+ return cls(**data)
88
+
89
+ def to_compact_json(self) -> str:
90
+ """Compact JSON for DHT storage (minimize bandwidth)."""
91
+ # Only include non-zero fields
92
+ compact = {
93
+ "n": self.node_id[:16], # Abbreviated node_id (wallet identifier)
94
+ "t": self.timestamp,
95
+ "p": self.proof_type[0], # "t"=training, "i"=inference, "u"=uptime
96
+ "r": round(self.reward, 6),
97
+ "s": self.signature[:32] + "..." + self.signature[-8:], # Abbreviated signature
98
+ }
99
+
100
+ # Add optional fields only if present
101
+ if self.uptime_seconds > 0:
102
+ compact["u"] = self.uptime_seconds
103
+ if self.tokens_processed > 0:
104
+ compact["tk"] = self.tokens_processed
105
+ if self.training_batches > 0:
106
+ compact["tb"] = self.training_batches
107
+ if self.data_samples > 0:
108
+ compact["ds"] = self.data_samples
109
+ if self.layers_held > 0:
110
+ compact["l"] = self.layers_held
111
+ if self.has_embedding:
112
+ compact["e"] = 1
113
+ if self.has_lm_head:
114
+ compact["h"] = 1
115
+
116
+ return json.dumps(compact, separators=(',', ':')) # No whitespace
117
+
118
+
119
+ class DHTProofStore:
120
+ """
121
+ DHT-based proof storage manager.
122
+
123
+ Handles storing and retrieving proofs from Kademlia DHT.
124
+ """
125
+
126
+ def __init__(self, dht_protocol=None):
127
+ """
128
+ Initialize DHT proof store.
129
+
130
+ Args:
131
+ dht_protocol: Instance of DHTProtocol for DHT operations
132
+ """
133
+ self.dht = dht_protocol
134
+
135
+ def get_dht_key_for_wallet(self, wallet_id: str) -> int:
136
+ """
137
+ Calculate DHT key for a wallet.
138
+
139
+ The key is SHA256(wallet_id) to ensure deterministic placement
140
+ in the DHT and load balancing across nodes.
141
+
142
+ Args:
143
+ wallet_id: Wallet identifier (first 16 chars of node_id)
144
+
145
+ Returns:
146
+ 160-bit integer key for DHT storage
147
+ """
148
+ # Use SHA1 for DHT (Kademlia standard is 160-bit)
149
+ return int(hashlib.sha1(f"proofs:{wallet_id}".encode()).hexdigest(), 16)
150
+
151
+ def store_proof_in_dht(
152
+ self,
153
+ wallet_id: str,
154
+ proof_record: DHTProofRecord,
155
+ desired_replication: int = 3
156
+ ) -> bool:
157
+ """
158
+ Store a proof in DHT with adaptive replication.
159
+
160
+ The proof is stored at DHT key = SHA256(wallet_id) and replicated
161
+ to k closest nodes. The replication factor adapts to network size:
162
+ - 1 node: Local DB only (no replication)
163
+ - 2 nodes: k=1 (store on 1 peer)
164
+ - 3+ nodes: k=3 (full replication)
165
+
166
+ Args:
167
+ wallet_id: Wallet identifier
168
+ proof_record: Proof to store
169
+ desired_replication: Desired replication factor (default: 3)
170
+
171
+ Returns:
172
+ True if stored on at least one node (or 0 nodes if network too small)
173
+ """
174
+ if not self.dht:
175
+ logger.warning("DHT not available - proof not stored in DHT")
176
+ return False
177
+
178
+ try:
179
+ # Calculate DHT key
180
+ dht_key = self.get_dht_key_for_wallet(wallet_id)
181
+
182
+ # Serialize proof to JSON
183
+ proof_json = json.dumps(proof_record.to_dict())
184
+
185
+ # ADAPTIVE REPLICATION: Adjust k to available network size
186
+ # Get all nodes in routing table
187
+ all_nodes = self.dht.routing_table.get_all_nodes()
188
+ available_peers = len(all_nodes) # Excludes self
189
+
190
+ # Adapt replication factor to network size
191
+ # For 2-node network: k=1 (store on 1 peer)
192
+ # For 3+ node network: k=3 (full replication)
193
+ actual_replication = min(desired_replication, max(1, available_peers))
194
+
195
+ # Find k closest nodes to this key
196
+ closest_nodes = self.dht.routing_table.find_closest(dht_key, k=actual_replication)
197
+
198
+ if not closest_nodes:
199
+ if available_peers == 0:
200
+ # Solo node - can't use DHT yet
201
+ logger.debug(f"Solo node - no peers for DHT replication (wallet {wallet_id[:8]}...)")
202
+ logger.debug(f"Proofs will be stored in DHT when network has >=2 nodes")
203
+ return False # Not an error - just need more nodes
204
+ else:
205
+ logger.warning(f"No DHT nodes found for wallet {wallet_id[:8]}... despite {available_peers} peers")
206
+ return False
207
+
208
+ # Store on each node
209
+ success_count = 0
210
+ for node in closest_nodes:
211
+ try:
212
+ if self.dht.store(node, dht_key, proof_json):
213
+ success_count += 1
214
+ except Exception as e:
215
+ logger.debug(f"Failed to store proof on node {node}: {e}")
216
+
217
+ if success_count > 0:
218
+ # Show network size context
219
+ if available_peers < 3:
220
+ logger.info(f"Stored proof on {success_count} node(s) (network has {available_peers+1} nodes - need 3+ for full replication)")
221
+ else:
222
+ logger.debug(f"Stored proof for wallet {wallet_id[:8]}... on {success_count}/{len(closest_nodes)} DHT nodes")
223
+ return True
224
+ else:
225
+ logger.warning(f"Failed to store proof on any DHT nodes for wallet {wallet_id[:8]}...")
226
+ return False
227
+
228
+ except Exception as e:
229
+ logger.error(f"DHT proof storage error: {e}")
230
+ return False
231
+
232
+ def retrieve_proofs_from_dht(
233
+ self,
234
+ wallet_id: str,
235
+ max_proofs: int = 100,
236
+ verify_signatures: bool = True
237
+ ) -> Tuple[List[DHTProofRecord], Dict[str, any]]:
238
+ """
239
+ Retrieve proofs for a wallet from DHT with verification.
240
+
241
+ This performs a multi-step trustless retrieval:
242
+ 1. Query DHT for wallet's proofs
243
+ 2. Verify ECDSA signatures on each proof
244
+ 3. Cross-validate with multiple DHT nodes
245
+ 4. Return only verified proofs
246
+
247
+ Args:
248
+ wallet_id: Wallet identifier
249
+ max_proofs: Maximum number of proofs to retrieve
250
+ verify_signatures: Whether to verify ECDSA signatures (default: True)
251
+
252
+ Returns:
253
+ (verified_proofs, metadata)
254
+ metadata includes: total_reward, proof_count, retrieval_stats
255
+ """
256
+ if not self.dht:
257
+ logger.warning("DHT not available - cannot retrieve proofs")
258
+ return [], {"error": "DHT not available"}
259
+
260
+ try:
261
+ # Calculate DHT key
262
+ dht_key = self.get_dht_key_for_wallet(wallet_id)
263
+
264
+ # Query DHT for value
265
+ logger.info(f"Querying DHT for wallet {wallet_id[:8]}... proofs (key={hex(dht_key)[:10]}...)")
266
+
267
+ value_json = self.dht.lookup_value(dht_key)
268
+
269
+ if not value_json:
270
+ logger.info(f"No proofs found in DHT for wallet {wallet_id[:8]}...")
271
+ return [], {
272
+ "found": False,
273
+ "wallet_id": wallet_id,
274
+ "message": "No proofs in DHT (new wallet or network still syncing)"
275
+ }
276
+
277
+ # Parse JSON list of proofs
278
+ try:
279
+ proof_list = json.loads(value_json)
280
+ if not isinstance(proof_list, list):
281
+ proof_list = [value_json] # Single proof
282
+ except json.JSONDecodeError:
283
+ # Legacy format or single proof
284
+ proof_list = [value_json]
285
+
286
+ logger.info(f"Found {len(proof_list)} proof records in DHT for wallet {wallet_id[:8]}...")
287
+
288
+ # Parse and verify each proof
289
+ verified_proofs = []
290
+ total_reward = 0.0
291
+ verification_failures = 0
292
+
293
+ for i, proof_json in enumerate(proof_list[:max_proofs]):
294
+ try:
295
+ # Parse proof record
296
+ if isinstance(proof_json, str):
297
+ proof_data = json.loads(proof_json)
298
+ else:
299
+ proof_data = proof_json
300
+
301
+ proof_record = DHTProofRecord.from_dict(proof_data)
302
+
303
+ # 🔒 SECURITY: ALWAYS verify signatures (trustless system)
304
+ if verify_signatures:
305
+ # Import crypto module for verification
306
+ from neuroshard.core.crypto.ecdsa import verify_signature
307
+
308
+ # 🚨 CRITICAL: Reject proof if no public key
309
+ if not proof_record.public_key:
310
+ verification_failures += 1
311
+ logger.warning(f"❌ REJECTED proof {i+1}: No public key included (node {proof_record.node_id[:16]}...)")
312
+ continue
313
+
314
+ # Reconstruct canonical payload EXACTLY as in ledger.py:146-156
315
+ # CRITICAL: Must match format from PoNWProof.canonical_payload()
316
+ payload = (
317
+ f"{proof_record.node_id}:{proof_record.proof_type}:{proof_record.timestamp:.6f}:{proof_record.nonce}:"
318
+ f"{float(proof_record.uptime_seconds):.1f}:{proof_record.tokens_processed}:{proof_record.training_batches}:"
319
+ f"{proof_record.data_samples}:{proof_record.model_hash}:{proof_record.layers_held}"
320
+ )
321
+
322
+ # Verify ECDSA signature with provided public key
323
+ # The verify_signature function will:
324
+ # 1. Validate public_key matches node_id
325
+ # 2. Verify ECDSA signature
326
+ # 3. Auto-register public key for future use
327
+ is_valid = verify_signature(
328
+ node_id=proof_record.node_id,
329
+ payload=payload,
330
+ signature=proof_record.signature,
331
+ public_key_hex=proof_record.public_key # Provide public key
332
+ )
333
+
334
+ if is_valid:
335
+ verified_proofs.append(proof_record)
336
+ total_reward += proof_record.reward
337
+ else:
338
+ verification_failures += 1
339
+ logger.warning(f"❌ REJECTED proof {i+1}: Invalid ECDSA signature (node {proof_record.node_id[:16]}...)")
340
+ else:
341
+ # No verification requested - accept all (DANGEROUS!)
342
+ verified_proofs.append(proof_record)
343
+ total_reward += proof_record.reward
344
+
345
+ except Exception as e:
346
+ logger.warning(f"Failed to parse/verify proof {i+1}: {e}")
347
+ verification_failures += 1
348
+
349
+ metadata = {
350
+ "found": True,
351
+ "wallet_id": wallet_id,
352
+ "total_proofs": len(proof_list),
353
+ "verified_proofs": len(verified_proofs),
354
+ "verification_failures": verification_failures,
355
+ "total_reward": round(total_reward, 6),
356
+ "dht_key": hex(dht_key),
357
+ "signature_verification": "enabled" if verify_signatures else "disabled"
358
+ }
359
+
360
+ logger.info(f"DHT retrieval complete: {len(verified_proofs)}/{len(proof_list)} proofs verified, "
361
+ f"total_reward={total_reward:.6f} NEURO")
362
+
363
+ return verified_proofs, metadata
364
+
365
+ except Exception as e:
366
+ logger.error(f"DHT proof retrieval error: {e}")
367
+ return [], {"error": str(e)}
368
+
369
+ def cross_validate_proofs(
370
+ self,
371
+ wallet_id: str,
372
+ desired_validators: int = 3
373
+ ) -> Tuple[bool, Dict[str, any]]:
374
+ """
375
+ Cross-validate proof data with multiple DHT nodes (ADAPTIVE).
376
+
377
+ Queries multiple independent DHT nodes and ensures consensus
378
+ on wallet balance. Adapts to network size:
379
+ - 1 node: Skip validation (only local DB exists)
380
+ - 2 nodes: Validate with both (need 100% agreement)
381
+ - 3+ nodes: Validate with 3+ (need majority)
382
+
383
+ Args:
384
+ wallet_id: Wallet to validate
385
+ desired_validators: Desired number of validators (default: 3)
386
+
387
+ Returns:
388
+ (consensus_reached, validation_data)
389
+ """
390
+ if not self.dht:
391
+ return False, {"error": "DHT not available"}
392
+
393
+ try:
394
+ dht_key = self.get_dht_key_for_wallet(wallet_id)
395
+
396
+ # ADAPTIVE VALIDATION: Adjust to network size
397
+ all_nodes = self.dht.routing_table.get_all_nodes()
398
+ available_peers = len(all_nodes)
399
+
400
+ # Calculate actual validators needed
401
+ # For small networks: Use all available peers
402
+ # For large networks: Use desired_validators (usually 3)
403
+ actual_validators = min(desired_validators, available_peers)
404
+
405
+ if actual_validators == 0:
406
+ # Solo node - no cross-validation possible
407
+ logger.info(f"Solo node - skipping cross-validation (network needs >=2 nodes)")
408
+ return True, {
409
+ "consensus": True,
410
+ "validators_queried": 0,
411
+ "network_size": 1,
412
+ "message": "Solo node - cross-validation skipped (not needed)"
413
+ }
414
+
415
+ # Find multiple nodes that should have this data
416
+ closest_nodes = self.dht.routing_table.find_closest(dht_key, k=actual_validators * 2)
417
+
418
+ if len(closest_nodes) < actual_validators:
419
+ # Network smaller than expected - adjust
420
+ actual_validators = len(closest_nodes)
421
+
422
+ if actual_validators == 0:
423
+ logger.warning(f"No DHT nodes reachable for cross-validation")
424
+ # Not enough nodes - accept optimistically for small networks
425
+ return True, {
426
+ "consensus": True,
427
+ "validators_queried": 0,
428
+ "network_size": available_peers,
429
+ "message": "Network too small for cross-validation - accepted optimistically"
430
+ }
431
+
432
+ # Query each node independently
433
+ responses = []
434
+ for node in closest_nodes[:actual_validators]:
435
+ try:
436
+ value, _ = self.dht.find_value(node, dht_key)
437
+ if value:
438
+ # Parse and count proofs
439
+ proof_list = json.loads(value)
440
+ if not isinstance(proof_list, list):
441
+ proof_list = [value]
442
+
443
+ # Calculate total reward
444
+ total = sum(
445
+ DHTProofRecord.from_dict(json.loads(p) if isinstance(p, str) else p).reward
446
+ for p in proof_list
447
+ )
448
+
449
+ responses.append({
450
+ "node": f"{node.ip}:{node.port}",
451
+ "proof_count": len(proof_list),
452
+ "total_reward": round(total, 6)
453
+ })
454
+ except Exception as e:
455
+ logger.debug(f"Failed to query node {node}: {e}")
456
+
457
+ # ADAPTIVE CONSENSUS RULES:
458
+ # - 1 node: Auto-pass (solo mining)
459
+ # - 2 nodes: Both must agree (100% consensus required)
460
+ # - 3+ nodes: Majority consensus (allow 1% deviation)
461
+
462
+ if len(responses) == 0:
463
+ # No responses - network too small or unreachable
464
+ return True, {
465
+ "consensus": True,
466
+ "validators_queried": 0,
467
+ "message": "No validators available - using local data only"
468
+ }
469
+
470
+ if len(responses) == 1:
471
+ # Only 1 validator (2-node network)
472
+ # Accept if we got a response (basic sanity check)
473
+ return True, {
474
+ "consensus": True,
475
+ "validators_queried": 1,
476
+ "network_size": 2,
477
+ "message": "2-node network - single validator confirmation",
478
+ "responses": responses
479
+ }
480
+
481
+ # 2+ validators: Check consensus (all nodes should report similar totals)
482
+ rewards = [r["total_reward"] for r in responses]
483
+ avg_reward = sum(rewards) / len(rewards)
484
+ max_deviation = max(abs(r - avg_reward) for r in rewards)
485
+
486
+ # ADAPTIVE THRESHOLD:
487
+ # - 2 nodes: Require EXACT match (0% deviation)
488
+ # - 3+ nodes: Allow 1% deviation (timing differences)
489
+ deviation_threshold = 0.0 if len(responses) == 2 else (avg_reward * 0.01)
490
+ consensus = max_deviation <= deviation_threshold if avg_reward > 0 else True
491
+
492
+ validation_data = {
493
+ "consensus": consensus,
494
+ "validators_queried": len(responses),
495
+ "network_size": available_peers + 1, # +1 for self
496
+ "avg_reward": round(avg_reward, 6),
497
+ "max_deviation": round(max_deviation, 6),
498
+ "deviation_threshold": round(deviation_threshold, 6),
499
+ "responses": responses
500
+ }
501
+
502
+ if consensus:
503
+ logger.info(f"Cross-validation PASSED for wallet {wallet_id[:8]}... "
504
+ f"({len(responses)} nodes agree, avg={avg_reward:.6f} NEURO, "
505
+ f"network_size={available_peers + 1})")
506
+ else:
507
+ logger.warning(f"Cross-validation FAILED for wallet {wallet_id[:8]}... "
508
+ f"(deviation={max_deviation:.6f} > threshold={deviation_threshold:.6f}, "
509
+ f"avg={avg_reward:.6f})")
510
+
511
+ return consensus, validation_data
512
+
513
+ except Exception as e:
514
+ logger.error(f"Cross-validation error: {e}")
515
+ return False, {"error": str(e)}
516
+