GitHub Action commited on
Commit
e6c82a2
·
1 Parent(s): ad45f9a

Sync from GitHub with Git LFS

Browse files
Files changed (2) hide show
  1. agents/peer_sync.py +245 -88
  2. agents/tools/storage.py +26 -2
agents/peer_sync.py CHANGED
@@ -135,91 +135,182 @@ def load_bootstrap_peers(filename="bootstrap.txt"):
135
  # ---------------------------
136
  def udp_discovery():
137
  DISCOVERY_INTERVAL = 30
138
- local_addresses = storage.get_config_value("local_addresses", [])
139
- msg_data = json.dumps({
140
- "id": my_id,
141
- "name": agent_name,
142
- "addresses": local_addresses
143
- }).encode("utf-8")
144
-
145
- # Создаём UDP сокеты для прослушки
146
- listen_sockets = []
147
- for port in local_ports:
148
- # IPv4
149
- try:
150
- sock4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
151
- sock4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
152
- sock4.bind(("", port))
153
- listen_sockets.append(sock4)
154
- print(f"[UDP Discovery] Listening IPv4 on *:{port}")
155
- except Exception as e:
156
- print(f"[UDP Discovery] IPv4 bind failed on port {port}: {e}")
157
 
158
- # IPv6
159
  try:
160
- sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
161
- sock6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
162
- sock6.bind(("::", port))
163
- listen_sockets.append(sock6)
164
- print(f"[UDP Discovery] Listening IPv6 on [::]:{port}")
165
- except Exception as e:
166
- print(f"[UDP Discovery] IPv6 bind failed on port {port}: {e}")
 
 
 
 
 
 
 
 
167
 
168
- while True:
169
- # Приём сообщений
170
- rlist, _, _ = select.select(listen_sockets, [], [], 0.5)
171
- for sock in rlist:
172
- try:
173
- data, addr = sock.recvfrom(2048)
174
- msg = json.loads(data.decode("utf-8"))
175
- peer_id = msg.get("id")
176
- if peer_id == my_id:
177
  continue
178
- name = msg.get("name", "unknown")
179
- addresses = msg.get("addresses", [])
180
- storage.add_or_update_peer(peer_id, name, addresses, "discovery", "online")
181
- print(f"[UDP Discovery] peer={peer_id} from {addr}")
182
- except Exception as e:
183
- print(f"[UDP Discovery] receive error: {e}")
184
-
185
- # Отправка broadcast/multicast
186
- for port in local_ports:
187
- # IPv4 broadcast
188
- for iface in netifaces.interfaces():
189
- addrs = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
190
- for a in addrs:
191
- if "broadcast" in a:
192
- try:
193
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
194
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
195
- sock.sendto(msg_data, (a["broadcast"], port))
196
- sock.close()
197
- except Exception:
198
- continue
199
- # IPv6 multicast ff02::1
200
- for iface in netifaces.interfaces():
201
- ifaddrs = netifaces.ifaddresses(iface).get(netifaces.AF_INET6, [])
202
- for a in ifaddrs:
203
- addr = a.get("addr")
204
- if not addr:
205
- continue
206
- multicast_addr = f"ff02::1%{iface}" if addr.startswith("fe80:") else "ff02::1"
207
  try:
208
  sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
209
- sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, socket.if_nametoindex(iface))
210
- sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
211
- sock6.sendto(msg_data, (multicast_addr, port))
212
- sock6.close()
213
- except Exception:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  continue
215
 
216
- time.sleep(DISCOVERY_INTERVAL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
  # ---------------------------
219
  # TCP Peer Exchange (исходящие)
220
  # ---------------------------
221
  def tcp_peer_exchange():
222
- PEER_EXCHANGE_INTERVAL = 20 # для отладки
 
223
  while True:
224
  peers = storage.get_known_peers(my_id, limit=50)
225
  print(f"[PeerExchange] Checking {len(peers)} peers (raw DB)...")
@@ -237,10 +328,33 @@ def tcp_peer_exchange():
237
  print(f"[PeerExchange] JSON decode error for peer {peer_id}: {e}")
238
  addr_list = []
239
 
240
- for addr in addr_list:
241
- norm = storage.normalize_address(addr)
 
 
 
 
 
 
 
 
242
  if not norm:
243
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  proto, hostport = norm.split("://", 1)
245
  if proto not in ["tcp", "any"]:
246
  continue
@@ -249,6 +363,7 @@ def tcp_peer_exchange():
249
  continue
250
 
251
  print(f"[PeerExchange] Trying {peer_id} at {host}:{port} (proto={proto})")
 
252
  try:
253
  # IPv6 link-local
254
  if storage.is_ipv6(host) and host.startswith("fe80:"):
@@ -265,12 +380,14 @@ def tcp_peer_exchange():
265
  sock.settimeout(3)
266
  sock.connect((host, port))
267
 
268
- # LAN или Интернет
269
  if storage.is_private(host):
270
  send_addresses = all_addresses
271
  else:
272
- send_addresses = [a for a in all_addresses
273
- if is_public(stprage.parse_hostport(a.split("://", 1)[1])[0])]
 
 
274
 
275
  handshake = {
276
  "type": "PEER_EXCHANGE_REQUEST",
@@ -290,10 +407,20 @@ def tcp_peer_exchange():
290
  try:
291
  peers_recv = json.loads(data.decode("utf-8"))
292
  for p in peers_recv:
293
- if p.get("id") and p["id"] != my_id:
 
 
 
 
 
 
 
294
  storage.add_or_update_peer(
295
- p["id"], p.get("name", "unknown"), p.get("addresses", []),
296
- "peer_exchange", "online"
 
 
 
297
  )
298
  print(f"[PeerExchange] Received {len(peers_recv)} peers from {host}:{port}")
299
  except Exception as e:
@@ -351,16 +478,47 @@ def tcp_listener():
351
  peer_name = msg.get("name", "unknown")
352
  peer_addrs = msg.get("addresses", [])
353
 
354
- storage.add_or_update_peer(peer_id, peer_name, peer_addrs,
355
- source="incoming", status="online")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  print(f"[TCP Listener] Handshake from {peer_id} ({addr})")
357
 
358
- # LAN или Интернет
359
  is_lan = storage.is_private(addr[0])
360
-
361
  peers_list = []
 
362
  for peer in storage.get_known_peers(my_id, limit=50):
363
- peer_id = peer["id"]
364
  try:
365
  addresses = json.loads(peer["addresses"])
366
  except:
@@ -368,7 +526,7 @@ def tcp_listener():
368
 
369
  updated_addresses = []
370
  for a in addresses:
371
- proto, hostport = a.split("://")
372
  host, port = storage.parse_hostport(hostport)
373
 
374
  # Фильтруем по LAN/Internet
@@ -383,10 +541,9 @@ def tcp_listener():
383
 
384
  updated_addresses.append(f"{proto}://{host}:{port}")
385
 
386
- peers_list.append({"id": peer_id, "addresses": updated_addresses})
387
 
388
  conn.sendall(json.dumps(peers_list).encode("utf-8"))
389
-
390
  conn.close()
391
  except Exception as e:
392
  print(f"[TCP Listener] Connection handling error: {e}")
 
135
  # ---------------------------
136
  def udp_discovery():
137
  DISCOVERY_INTERVAL = 30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ while True:
140
  try:
141
+ # Получаем локальные адреса из storage
142
+ local_addresses = storage.get_config_value("local_addresses", [])
143
+ msg_data = json.dumps({
144
+ "id": my_id,
145
+ "name": agent_name,
146
+ "addresses": local_addresses
147
+ }).encode("utf-8")
148
+
149
+ # Создаём UDP сокеты для прослушки
150
+ listen_sockets = []
151
+ for entry in local_addresses:
152
+ addr_str = entry.get("addr") if isinstance(entry, dict) else entry
153
+ pubkey = entry.get("pubkey") if isinstance(entry, dict) else None
154
+ if not addr_str:
155
+ continue
156
 
157
+ proto, hostport = addr_str.split("://", 1)
158
+ host, port = storage.parse_hostport(hostport)
159
+ if not port or proto.lower() != "udp":
 
 
 
 
 
 
160
  continue
161
+
162
+ # IPv4
163
+ if not host.startswith("["):
164
+ try:
165
+ sock4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
166
+ sock4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
167
+ sock4.bind(("", port))
168
+ listen_sockets.append(sock4)
169
+ print(f"[UDP Discovery] Listening IPv4 on *:{port}")
170
+ except Exception as e:
171
+ print(f"[UDP Discovery] IPv4 bind failed on port {port}: {e}")
172
+
173
+ # IPv6
174
+ else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  try:
176
  sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
177
+ sock6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
178
+ sock6.bind(("::", port))
179
+ listen_sockets.append(sock6)
180
+ print(f"[UDP Discovery] Listening IPv6 on [::]:{port}")
181
+ except Exception as e:
182
+ print(f"[UDP Discovery] IPv6 bind failed on port {port}: {e}")
183
+
184
+ # Приём сообщений
185
+ if listen_sockets:
186
+ rlist, _, _ = select.select(listen_sockets, [], [], 0.5)
187
+ for sock in rlist:
188
+ try:
189
+ data, addr = sock.recvfrom(2048)
190
+ msg = json.loads(data.decode("utf-8"))
191
+ peer_id = msg.get("id")
192
+ if peer_id == my_id:
193
+ continue
194
+ name = msg.get("name", "unknown")
195
+ addresses = msg.get("addresses", [])
196
+
197
+ # Фильтруем адреса по PoW и datetime
198
+ valid_addresses = []
199
+ for a in addresses:
200
+ addr_str = a.get("addr")
201
+ nonce = a.get("nonce")
202
+ pow_hash = a.get("pow_hash")
203
+ difficulty = a.get("difficulty")
204
+ dt = a.get("datetime")
205
+ pubkey = a.get("pubkey")
206
+
207
+ if not addr_str:
208
+ continue
209
+
210
+ # Проверка PoW
211
+ if nonce is not None and pow_hash and difficulty is not None and pubkey:
212
+ if not storage.verify_pow(peer_id, pubkey, addr_str, nonce, pow_hash, dt, difficulty):
213
+ continue
214
+
215
+ # Проверяем datetime
216
+ existing = storage.get_peer_address(peer_id, addr_str)
217
+ if existing:
218
+ existing_dt = existing.get("datetime")
219
+ if existing_dt and existing_dt >= dt:
220
+ continue
221
+
222
+ valid_addresses.append(a)
223
+
224
+ if valid_addresses:
225
+ storage.add_or_update_peer(
226
+ peer_id=peer_id,
227
+ name=name,
228
+ addresses=valid_addresses,
229
+ source="discovery",
230
+ status="online"
231
+ )
232
+ print(f"[UDP Discovery] peer={peer_id} from {addr}")
233
+
234
+ except Exception as e:
235
+ print(f"[UDP Discovery] receive error: {e}")
236
+
237
+ # Отправка broadcast/multicast с фильтрацией по PoW и datetime
238
+ valid_local_addresses = []
239
+ for a in local_addresses:
240
+ addr_str = a.get("addr") if isinstance(a, dict) else a
241
+ nonce = a.get("nonce")
242
+ pow_hash = a.get("pow_hash")
243
+ difficulty = a.get("difficulty")
244
+ dt = a.get("datetime")
245
+ pubkey = a.get("pubkey") if isinstance(a, dict) else None
246
+
247
+ if not addr_str or not pubkey:
248
+ continue
249
+
250
+ # Проверка PoW
251
+ if nonce is not None and pow_hash and difficulty is not None:
252
+ if not storage.verify_pow(my_id, pubkey, addr_str, nonce, pow_hash, dt, difficulty):
253
  continue
254
 
255
+ valid_local_addresses.append(a)
256
+
257
+ msg_data = json.dumps({
258
+ "id": my_id,
259
+ "name": agent_name,
260
+ "addresses": valid_local_addresses
261
+ }).encode("utf-8")
262
+
263
+ for entry in valid_local_addresses:
264
+ addr_str = entry.get("addr")
265
+ proto, hostport = addr_str.split("://", 1)
266
+ host, port = storage.parse_hostport(hostport)
267
+ if not port or proto.lower() != "udp":
268
+ continue
269
+
270
+ # IPv4 broadcast
271
+ if not host.startswith("["):
272
+ for iface in netifaces.interfaces():
273
+ addrs = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
274
+ for a in addrs:
275
+ if "broadcast" in a:
276
+ try:
277
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
278
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
279
+ sock.sendto(msg_data, (a["broadcast"], port))
280
+ sock.close()
281
+ except Exception:
282
+ continue
283
+
284
+ # IPv6 multicast ff02::1
285
+ else:
286
+ for iface in netifaces.interfaces():
287
+ ifaddrs = netifaces.ifaddresses(iface).get(netifaces.AF_INET6, [])
288
+ for a in ifaddrs:
289
+ addr_ipv6 = a.get("addr")
290
+ if not addr_ipv6:
291
+ continue
292
+ multicast_addr = f"ff02::1%{iface}" if addr_ipv6.startswith("fe80:") else "ff02::1"
293
+ try:
294
+ sock6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
295
+ sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, socket.if_nametoindex(iface))
296
+ sock6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1)
297
+ sock6.sendto(msg_data, (multicast_addr, port))
298
+ sock6.close()
299
+ except Exception:
300
+ continue
301
+
302
+ time.sleep(DISCOVERY_INTERVAL)
303
+
304
+ except Exception as main_e:
305
+ print(f"[UDP Discovery] main loop error: {main_e}")
306
+ time.sleep(DISCOVERY_INTERVAL)
307
 
308
  # ---------------------------
309
  # TCP Peer Exchange (исходящие)
310
  # ---------------------------
311
  def tcp_peer_exchange():
312
+ PEER_EXCHANGE_INTERVAL = 20 # секунды для отладки
313
+
314
  while True:
315
  peers = storage.get_known_peers(my_id, limit=50)
316
  print(f"[PeerExchange] Checking {len(peers)} peers (raw DB)...")
 
328
  print(f"[PeerExchange] JSON decode error for peer {peer_id}: {e}")
329
  addr_list = []
330
 
331
+ for addr_entry in addr_list:
332
+ addr_str = addr_entry.get("addr")
333
+ nonce = addr_entry.get("nonce")
334
+ pow_hash = addr_entry.get("pow_hash")
335
+ difficulty = addr_entry.get("difficulty")
336
+ dt = addr_entry.get("datetime")
337
+ pubkey = addr_entry.get("pubkey")
338
+
339
+ # Нормализация адреса
340
+ norm = storage.normalize_address(addr_str)
341
  if not norm:
342
  continue
343
+
344
+ # Проверка PoW
345
+ if nonce is not None and pow_hash and difficulty is not None and pubkey:
346
+ if not storage.verify_pow(peer_id, pubkey, addr_str, nonce, pow_hash, dt, difficulty):
347
+ print(f"[PeerExchange] PoW check failed for {addr_str}")
348
+ continue
349
+
350
+ # Проверяем datetime
351
+ existing = storage.get_peer_address(peer_id, addr_str)
352
+ if existing:
353
+ existing_dt = existing.get("datetime")
354
+ if existing_dt and existing_dt >= dt:
355
+ continue # старый адрес, пропускаем
356
+
357
+ # Парсим host и port
358
  proto, hostport = norm.split("://", 1)
359
  if proto not in ["tcp", "any"]:
360
  continue
 
363
  continue
364
 
365
  print(f"[PeerExchange] Trying {peer_id} at {host}:{port} (proto={proto})")
366
+
367
  try:
368
  # IPv6 link-local
369
  if storage.is_ipv6(host) and host.startswith("fe80:"):
 
380
  sock.settimeout(3)
381
  sock.connect((host, port))
382
 
383
+ # Отправка своих адресов
384
  if storage.is_private(host):
385
  send_addresses = all_addresses
386
  else:
387
+ send_addresses = [
388
+ a for a in all_addresses
389
+ if is_public(storage.parse_hostport(a.split("://", 1)[1])[0])
390
+ ]
391
 
392
  handshake = {
393
  "type": "PEER_EXCHANGE_REQUEST",
 
407
  try:
408
  peers_recv = json.loads(data.decode("utf-8"))
409
  for p in peers_recv:
410
+ # Сохраняем только новые адреса или более новые datetime
411
+ new_addrs = []
412
+ for a in p.get("addresses", []):
413
+ existing_addr = storage.get_peer_address(p["id"], a.get("addr"))
414
+ if existing_addr is None or existing_addr.get("datetime", "") < a.get("datetime", ""):
415
+ new_addrs.append(a)
416
+
417
+ if new_addrs:
418
  storage.add_or_update_peer(
419
+ p["id"],
420
+ p.get("name", "unknown"),
421
+ new_addrs,
422
+ source="peer_exchange",
423
+ status="online"
424
  )
425
  print(f"[PeerExchange] Received {len(peers_recv)} peers from {host}:{port}")
426
  except Exception as e:
 
478
  peer_name = msg.get("name", "unknown")
479
  peer_addrs = msg.get("addresses", [])
480
 
481
+ # Добавляем/обновляем пира только если PoW валидный и datetime новее
482
+ valid_addrs = []
483
+ for a in peer_addrs:
484
+ addr_value = a.get("addr")
485
+ nonce = a.get("nonce")
486
+ pow_hash = a.get("pow_hash")
487
+ difficulty = a.get("difficulty")
488
+ dt = a.get("datetime")
489
+ pubkey = a.get("pubkey")
490
+
491
+ if not addr_value or nonce is None or not pow_hash or not pubkey:
492
+ continue
493
+
494
+ if not storage.verify_pow(peer_id, pubkey, addr_value, nonce, pow_hash, dt, difficulty):
495
+ continue
496
+
497
+ existing = storage.get_peer_address(peer_id, addr_value)
498
+ if existing:
499
+ existing_dt = existing.get("datetime")
500
+ if existing_dt and existing_dt >= dt:
501
+ continue # старый адрес
502
+
503
+ valid_addrs.append(a)
504
+
505
+ if valid_addrs:
506
+ storage.add_or_update_peer(
507
+ peer_id=peer_id,
508
+ name=peer_name,
509
+ addresses=valid_addrs,
510
+ source="incoming",
511
+ status="online"
512
+ )
513
+
514
  print(f"[TCP Listener] Handshake from {peer_id} ({addr})")
515
 
516
+ # Отправляем актуальные адреса собеседнику
517
  is_lan = storage.is_private(addr[0])
 
518
  peers_list = []
519
+
520
  for peer in storage.get_known_peers(my_id, limit=50):
521
+ peer_id_local = peer["id"]
522
  try:
523
  addresses = json.loads(peer["addresses"])
524
  except:
 
526
 
527
  updated_addresses = []
528
  for a in addresses:
529
+ proto, hostport = a["addr"].split("://")
530
  host, port = storage.parse_hostport(hostport)
531
 
532
  # Фильтруем по LAN/Internet
 
541
 
542
  updated_addresses.append(f"{proto}://{host}:{port}")
543
 
544
+ peers_list.append({"id": peer_id_local, "addresses": updated_addresses})
545
 
546
  conn.sendall(json.dumps(peers_list).encode("utf-8"))
 
547
  conn.close()
548
  except Exception as e:
549
  print(f"[TCP Listener] Connection handling error: {e}")
agents/tools/storage.py CHANGED
@@ -953,7 +953,12 @@ class Storage:
953
  return None
954
 
955
  @classmethod
956
- def normalize_address(cls, addr: str) -> str:
 
 
 
 
 
957
  addr = addr.strip()
958
  if not addr:
959
  return None
@@ -963,7 +968,6 @@ class Storage:
963
  proto, hostport = addr.split("://", 1)
964
  host, port = cls.parse_hostport(hostport)
965
 
966
- # IPv6 без квадратных скобок
967
  if cls.is_ipv6(host) and not host.startswith("["):
968
  host = f"[{host}]"
969
 
@@ -1134,6 +1138,26 @@ class Storage:
1134
  c.execute("SELECT id, addresses FROM agent_peers WHERE id != ? LIMIT ?", (my_id, limit))
1135
  return c.fetchall()
1136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
  # Утилиты
1138
  def close(self):
1139
  self.conn.close()
 
953
  return None
954
 
955
  @classmethod
956
+ def normalize_address(cls, addr) -> str:
957
+ if isinstance(addr, dict) and "addr" in addr:
958
+ addr = addr["addr"]
959
+ if not isinstance(addr, str):
960
+ return None
961
+
962
  addr = addr.strip()
963
  if not addr:
964
  return None
 
968
  proto, hostport = addr.split("://", 1)
969
  host, port = cls.parse_hostport(hostport)
970
 
 
971
  if cls.is_ipv6(host) and not host.startswith("["):
972
  host = f"[{host}]"
973
 
 
1138
  c.execute("SELECT id, addresses FROM agent_peers WHERE id != ? LIMIT ?", (my_id, limit))
1139
  return c.fetchall()
1140
 
1141
+ def get_peer_address(self, peer_id: str, addr_str: str):
1142
+ """Возвращает запись адреса пира по peer_id и addr_str, или None если не найден"""
1143
+ peers = self.get_known_peers(my_id="", limit=1000) # передаем "" чтобы не фильтровать по my_id
1144
+ for p in peers:
1145
+ pid = p["id"] if isinstance(p, dict) else p[0]
1146
+ addresses_json = p["addresses"] if isinstance(p, dict) else p[1]
1147
+
1148
+ if pid != peer_id:
1149
+ continue
1150
+
1151
+ try:
1152
+ addresses = json.loads(addresses_json)
1153
+ except Exception:
1154
+ continue
1155
+
1156
+ for a in addresses:
1157
+ if a.get("addr") == addr_str:
1158
+ return a
1159
+ return None
1160
+
1161
  # Утилиты
1162
  def close(self):
1163
  self.conn.close()