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,261 @@
|
|
|
1
|
+
import grpc
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
from concurrent import futures
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from protos import neuroshard_pb2
|
|
9
|
+
from protos import neuroshard_pb2_grpc
|
|
10
|
+
from neuroshard.core.network.dht import Node, RoutingTable, ID_BITS
|
|
11
|
+
from neuroshard.core.network.dht_service import node_to_proto, proto_to_node
|
|
12
|
+
from neuroshard.core.network.connection_pool import get_channel
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("DHT")
|
|
15
|
+
|
|
16
|
+
class DHTProtocol:
|
|
17
|
+
def __init__(self, local_node: Node, routing_table: RoutingTable, port: int):
|
|
18
|
+
self.local_node = local_node
|
|
19
|
+
self.routing_table = routing_table
|
|
20
|
+
self.port = port
|
|
21
|
+
# Initialize internal storage for DHT values (acting as a node)
|
|
22
|
+
self.storage = {}
|
|
23
|
+
# We don't manage the server here anymore, it's mixed into the main gRPC server
|
|
24
|
+
# Rate limiting for "no peers" messages (log max once per 60 seconds)
|
|
25
|
+
self._last_no_peers_log = {}
|
|
26
|
+
|
|
27
|
+
def _get_stub(self, target: Node):
|
|
28
|
+
# gRPC port assumption: http port + 1000
|
|
29
|
+
# If target.port is already the gRPC port, we use it directly.
|
|
30
|
+
# Our convention in p2p.py was: p_port = parsed.port or 80.
|
|
31
|
+
# And in grpc_server.py: grpc_port = port + 1000.
|
|
32
|
+
# So the node.port stored in DHT is likely the HTTP port.
|
|
33
|
+
|
|
34
|
+
# Let's assume Node stores HTTP port, so we add 1000.
|
|
35
|
+
# We should standardize this. For now, consistent with existing logic.
|
|
36
|
+
target_addr = f"{target.ip}:{target.port + 1000}"
|
|
37
|
+
channel = get_channel(target_addr)
|
|
38
|
+
return neuroshard_pb2_grpc.NeuroShardServiceStub(channel)
|
|
39
|
+
|
|
40
|
+
def ping(self, target: Node) -> bool:
|
|
41
|
+
"""Send PING to target node."""
|
|
42
|
+
try:
|
|
43
|
+
stub = self._get_stub(target)
|
|
44
|
+
req = neuroshard_pb2.DHTPingRequest(
|
|
45
|
+
sender=node_to_proto(self.local_node)
|
|
46
|
+
)
|
|
47
|
+
resp = stub.DHTPing(req, timeout=2.0)
|
|
48
|
+
# Update routing table with responder
|
|
49
|
+
self.routing_table.add_contact(proto_to_node(resp.responder))
|
|
50
|
+
return True
|
|
51
|
+
except grpc.RpcError as e:
|
|
52
|
+
logger.debug(f"Ping failed to {target}: {e}")
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def find_node(self, target: Node, search_id: int) -> List[Node]:
|
|
56
|
+
"""Ask target for nodes closest to search_id."""
|
|
57
|
+
try:
|
|
58
|
+
stub = self._get_stub(target)
|
|
59
|
+
req = neuroshard_pb2.DHTFindNodeRequest(
|
|
60
|
+
sender=node_to_proto(self.local_node),
|
|
61
|
+
target_id=search_id.to_bytes(20, byteorder='big')
|
|
62
|
+
)
|
|
63
|
+
resp = stub.DHTFindNode(req, timeout=5.0)
|
|
64
|
+
|
|
65
|
+
self.routing_table.add_contact(proto_to_node(resp.responder))
|
|
66
|
+
|
|
67
|
+
nodes = [proto_to_node(n) for n in resp.nodes]
|
|
68
|
+
return nodes
|
|
69
|
+
except grpc.RpcError:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
def store(self, target: Node, key: int, value: str) -> bool:
|
|
73
|
+
"""Ask target to store key=value."""
|
|
74
|
+
try:
|
|
75
|
+
stub = self._get_stub(target)
|
|
76
|
+
req = neuroshard_pb2.DHTStoreRequest(
|
|
77
|
+
sender=node_to_proto(self.local_node),
|
|
78
|
+
key=key.to_bytes(20, byteorder='big'),
|
|
79
|
+
value=value
|
|
80
|
+
)
|
|
81
|
+
resp = stub.DHTStore(req, timeout=5.0)
|
|
82
|
+
|
|
83
|
+
self.routing_table.add_contact(proto_to_node(resp.responder))
|
|
84
|
+
return resp.success
|
|
85
|
+
except grpc.RpcError:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
def find_value(self, target: Node, key: int):
|
|
89
|
+
"""Ask target for value at key."""
|
|
90
|
+
# Returns (value: str | None, nodes: List[Node])
|
|
91
|
+
try:
|
|
92
|
+
stub = self._get_stub(target)
|
|
93
|
+
req = neuroshard_pb2.DHTFindValueRequest(
|
|
94
|
+
sender=node_to_proto(self.local_node),
|
|
95
|
+
key=key.to_bytes(20, byteorder='big')
|
|
96
|
+
)
|
|
97
|
+
resp = stub.DHTFindValue(req, timeout=5.0)
|
|
98
|
+
|
|
99
|
+
self.routing_table.add_contact(proto_to_node(resp.responder))
|
|
100
|
+
|
|
101
|
+
if resp.found:
|
|
102
|
+
return (resp.value, [])
|
|
103
|
+
else:
|
|
104
|
+
nodes = [proto_to_node(n) for n in resp.nodes]
|
|
105
|
+
return (None, nodes)
|
|
106
|
+
except grpc.RpcError:
|
|
107
|
+
return (None, [])
|
|
108
|
+
|
|
109
|
+
# --- High Level Lookup Algorithm (Iterative) ---
|
|
110
|
+
|
|
111
|
+
def lookup_node(self, target_id: int) -> List[Node]:
|
|
112
|
+
"""
|
|
113
|
+
Standard Kademlia Lookup
|
|
114
|
+
"""
|
|
115
|
+
shortlist = self.routing_table.find_closest(target_id)
|
|
116
|
+
if not shortlist:
|
|
117
|
+
return []
|
|
118
|
+
|
|
119
|
+
visited = set()
|
|
120
|
+
visited.add(self.local_node.id) # Don't query self
|
|
121
|
+
|
|
122
|
+
active_queries = 0
|
|
123
|
+
alpha = 3
|
|
124
|
+
|
|
125
|
+
# Iterative lookup
|
|
126
|
+
converged = False
|
|
127
|
+
while not converged:
|
|
128
|
+
candidates = [n for n in shortlist if n.id not in visited][:alpha]
|
|
129
|
+
if not candidates:
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
results_found = False
|
|
133
|
+
for node in candidates:
|
|
134
|
+
visited.add(node.id)
|
|
135
|
+
|
|
136
|
+
new_nodes = self.find_node(node, target_id)
|
|
137
|
+
if new_nodes:
|
|
138
|
+
results_found = True
|
|
139
|
+
for n in new_nodes:
|
|
140
|
+
if n.id != self.local_node.id:
|
|
141
|
+
self.routing_table.add_contact(n)
|
|
142
|
+
# Add to shortlist if not already there
|
|
143
|
+
if n not in shortlist:
|
|
144
|
+
shortlist.append(n)
|
|
145
|
+
|
|
146
|
+
# Re-sort
|
|
147
|
+
shortlist.sort(key=lambda n: n.id ^ target_id)
|
|
148
|
+
# K-bucket size limit
|
|
149
|
+
shortlist = shortlist[:20]
|
|
150
|
+
|
|
151
|
+
if not results_found:
|
|
152
|
+
converged = True
|
|
153
|
+
|
|
154
|
+
return shortlist
|
|
155
|
+
|
|
156
|
+
def lookup_value(self, key: int) -> Optional[str]:
|
|
157
|
+
"""
|
|
158
|
+
Find a value in the DHT.
|
|
159
|
+
Returns the value if found, None otherwise.
|
|
160
|
+
"""
|
|
161
|
+
shortlist = self.routing_table.find_closest(key)
|
|
162
|
+
if not shortlist:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
visited = set()
|
|
166
|
+
visited.add(self.local_node.id)
|
|
167
|
+
|
|
168
|
+
converged = False
|
|
169
|
+
while not converged:
|
|
170
|
+
candidates = [n for n in shortlist if n.id not in visited][:3]
|
|
171
|
+
if not candidates:
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
results_found = False
|
|
175
|
+
for node in candidates:
|
|
176
|
+
visited.add(node.id)
|
|
177
|
+
|
|
178
|
+
val, nodes = self.find_value(node, key)
|
|
179
|
+
if val:
|
|
180
|
+
return val # Found it!
|
|
181
|
+
|
|
182
|
+
if nodes:
|
|
183
|
+
results_found = True
|
|
184
|
+
for n in nodes:
|
|
185
|
+
if n.id != self.local_node.id:
|
|
186
|
+
self.routing_table.add_contact(n)
|
|
187
|
+
if n not in shortlist:
|
|
188
|
+
shortlist.append(n)
|
|
189
|
+
|
|
190
|
+
shortlist.sort(key=lambda n: n.id ^ key)
|
|
191
|
+
shortlist = shortlist[:20]
|
|
192
|
+
|
|
193
|
+
if not results_found:
|
|
194
|
+
converged = True
|
|
195
|
+
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
def announce(self, key_string: str):
|
|
199
|
+
"""
|
|
200
|
+
Announce a string key (e.g. "layer_0") to the DHT.
|
|
201
|
+
The key is hashed to find the location.
|
|
202
|
+
Value is our connection info.
|
|
203
|
+
"""
|
|
204
|
+
import hashlib
|
|
205
|
+
import json
|
|
206
|
+
# Hash the key string to get the 160-bit Key ID
|
|
207
|
+
key_id = int(hashlib.sha1(key_string.encode()).hexdigest(), 16)
|
|
208
|
+
value = f"{self.local_node.ip}:{self.local_node.port}"
|
|
209
|
+
|
|
210
|
+
# CRITICAL: ALWAYS store locally so other nodes can find us!
|
|
211
|
+
# When we're a solo node, we ARE the closest node to any key.
|
|
212
|
+
# Without local storage, DHT lookups will fail.
|
|
213
|
+
try:
|
|
214
|
+
# Store as list to support multiple holders
|
|
215
|
+
existing = self.storage.get(key_id)
|
|
216
|
+
if existing:
|
|
217
|
+
holders = json.loads(existing)
|
|
218
|
+
if value not in holders:
|
|
219
|
+
holders.append(value)
|
|
220
|
+
else:
|
|
221
|
+
holders = [value]
|
|
222
|
+
self.storage[key_id] = json.dumps(holders)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.debug(f"Local DHT storage failed: {e}")
|
|
225
|
+
|
|
226
|
+
# Find K closest nodes to the KEY ID (for replication to other nodes)
|
|
227
|
+
nodes = self.lookup_node(key_id)
|
|
228
|
+
|
|
229
|
+
store_count = 0
|
|
230
|
+
for node in nodes:
|
|
231
|
+
try:
|
|
232
|
+
if self.store(node, key_id, value):
|
|
233
|
+
store_count += 1
|
|
234
|
+
except: pass
|
|
235
|
+
|
|
236
|
+
if store_count > 0:
|
|
237
|
+
# Rate limit success logs to avoid spam (heartbeats happen every 10s)
|
|
238
|
+
now = time.time()
|
|
239
|
+
last_success = getattr(self, '_last_announce_success', {}).get(key_string, 0)
|
|
240
|
+
if not hasattr(self, '_last_announce_success'):
|
|
241
|
+
self._last_announce_success = {}
|
|
242
|
+
|
|
243
|
+
# Only log first announce and then every 5 minutes
|
|
244
|
+
if last_success == 0 or (now - last_success) > 300:
|
|
245
|
+
logger.info(f"DHT Announce success: Stored '{key_string}' on {store_count} nodes.")
|
|
246
|
+
self._last_announce_success[key_string] = now
|
|
247
|
+
else:
|
|
248
|
+
# Rate limit all DHT announce logs to avoid spam
|
|
249
|
+
now = time.time()
|
|
250
|
+
last_log = self._last_no_peers_log.get(key_string, 0)
|
|
251
|
+
if now - last_log > 60: # Rate limit: once per minute per key
|
|
252
|
+
if len(nodes) == 0:
|
|
253
|
+
# No peers found - expected when you're the first/only node
|
|
254
|
+
logger.debug(f"DHT Announce: No peers found to store '{key_string}' (this is normal when you're the first node).")
|
|
255
|
+
elif len(nodes) <= 3:
|
|
256
|
+
# Few nodes found and store failed - common in small networks or Docker
|
|
257
|
+
logger.debug(f"DHT Announce: Store failed for '{key_string}' (found {len(nodes)} nodes). This is normal in small networks.")
|
|
258
|
+
else:
|
|
259
|
+
# Found many nodes but all failed - this might be a real problem
|
|
260
|
+
logger.warning(f"DHT Announce failed: Could not store '{key_string}' (found {len(nodes)} nodes but store failed).")
|
|
261
|
+
self._last_no_peers_log[key_string] = now
|