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.
- neuroshard/__init__.py +93 -0
- neuroshard/__main__.py +4 -0
- neuroshard/cli.py +466 -0
- neuroshard/core/__init__.py +92 -0
- neuroshard/core/consensus/verifier.py +252 -0
- neuroshard/core/crypto/__init__.py +20 -0
- neuroshard/core/crypto/ecdsa.py +392 -0
- neuroshard/core/economics/__init__.py +52 -0
- neuroshard/core/economics/constants.py +387 -0
- neuroshard/core/economics/ledger.py +2111 -0
- neuroshard/core/economics/market.py +975 -0
- neuroshard/core/economics/wallet.py +168 -0
- neuroshard/core/governance/__init__.py +74 -0
- neuroshard/core/governance/proposal.py +561 -0
- neuroshard/core/governance/registry.py +545 -0
- neuroshard/core/governance/versioning.py +332 -0
- neuroshard/core/governance/voting.py +453 -0
- neuroshard/core/model/__init__.py +30 -0
- neuroshard/core/model/dynamic.py +4186 -0
- neuroshard/core/model/llm.py +905 -0
- neuroshard/core/model/registry.py +164 -0
- neuroshard/core/model/scaler.py +387 -0
- neuroshard/core/model/tokenizer.py +568 -0
- neuroshard/core/network/__init__.py +56 -0
- neuroshard/core/network/connection_pool.py +72 -0
- neuroshard/core/network/dht.py +130 -0
- neuroshard/core/network/dht_plan.py +55 -0
- neuroshard/core/network/dht_proof_store.py +516 -0
- neuroshard/core/network/dht_protocol.py +261 -0
- neuroshard/core/network/dht_service.py +506 -0
- neuroshard/core/network/encrypted_channel.py +141 -0
- neuroshard/core/network/nat.py +201 -0
- neuroshard/core/network/nat_traversal.py +695 -0
- neuroshard/core/network/p2p.py +929 -0
- neuroshard/core/network/p2p_data.py +150 -0
- neuroshard/core/swarm/__init__.py +106 -0
- neuroshard/core/swarm/aggregation.py +729 -0
- neuroshard/core/swarm/buffers.py +643 -0
- neuroshard/core/swarm/checkpoint.py +709 -0
- neuroshard/core/swarm/compute.py +624 -0
- neuroshard/core/swarm/diloco.py +844 -0
- neuroshard/core/swarm/factory.py +1288 -0
- neuroshard/core/swarm/heartbeat.py +669 -0
- neuroshard/core/swarm/logger.py +487 -0
- neuroshard/core/swarm/router.py +658 -0
- neuroshard/core/swarm/service.py +640 -0
- neuroshard/core/training/__init__.py +29 -0
- neuroshard/core/training/checkpoint.py +600 -0
- neuroshard/core/training/distributed.py +1602 -0
- neuroshard/core/training/global_tracker.py +617 -0
- neuroshard/core/training/production.py +276 -0
- neuroshard/governance_cli.py +729 -0
- neuroshard/grpc_server.py +895 -0
- neuroshard/runner.py +3223 -0
- neuroshard/sdk/__init__.py +92 -0
- neuroshard/sdk/client.py +990 -0
- neuroshard/sdk/errors.py +101 -0
- neuroshard/sdk/types.py +282 -0
- neuroshard/tracker/__init__.py +0 -0
- neuroshard/tracker/server.py +864 -0
- neuroshard/ui/__init__.py +0 -0
- neuroshard/ui/app.py +102 -0
- neuroshard/ui/templates/index.html +1052 -0
- neuroshard/utils/__init__.py +0 -0
- neuroshard/utils/autostart.py +81 -0
- neuroshard/utils/hardware.py +121 -0
- neuroshard/utils/serialization.py +90 -0
- neuroshard/version.py +1 -0
- nexaroa-0.0.111.dist-info/METADATA +283 -0
- nexaroa-0.0.111.dist-info/RECORD +78 -0
- nexaroa-0.0.111.dist-info/WHEEL +5 -0
- nexaroa-0.0.111.dist-info/entry_points.txt +4 -0
- nexaroa-0.0.111.dist-info/licenses/LICENSE +190 -0
- nexaroa-0.0.111.dist-info/top_level.txt +2 -0
- protos/__init__.py +0 -0
- protos/neuroshard.proto +651 -0
- protos/neuroshard_pb2.py +160 -0
- 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
|
+
|